ruby-swagger 0.0.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ruby-swagger.rb +7 -0
  3. data/lib/ruby-swagger/data/contact.rb +49 -0
  4. data/lib/ruby-swagger/data/definitions.rb +49 -0
  5. data/lib/ruby-swagger/data/document.rb +181 -0
  6. data/lib/ruby-swagger/data/example.rb +29 -0
  7. data/lib/ruby-swagger/data/external_documentation.rb +24 -0
  8. data/lib/ruby-swagger/data/header.rb +34 -0
  9. data/lib/ruby-swagger/data/headers.rb +48 -0
  10. data/lib/ruby-swagger/data/info.rb +56 -0
  11. data/lib/ruby-swagger/data/items.rb +45 -0
  12. data/lib/ruby-swagger/data/license.rb +51 -0
  13. data/lib/ruby-swagger/data/mime.rb +31 -0
  14. data/lib/ruby-swagger/data/operation.rb +82 -0
  15. data/lib/ruby-swagger/data/parameter.rb +88 -0
  16. data/lib/ruby-swagger/data/parameters.rb +53 -0
  17. data/lib/ruby-swagger/data/path.rb +115 -0
  18. data/lib/ruby-swagger/data/paths.rb +50 -0
  19. data/lib/ruby-swagger/data/reference.rb +30 -0
  20. data/lib/ruby-swagger/data/response.rb +25 -0
  21. data/lib/ruby-swagger/data/responses.rb +50 -0
  22. data/lib/ruby-swagger/data/schema.rb +68 -0
  23. data/lib/ruby-swagger/data/scopes.rb +44 -0
  24. data/lib/ruby-swagger/data/security_definitions.rb +49 -0
  25. data/lib/ruby-swagger/data/security_requirement.rb +35 -0
  26. data/lib/ruby-swagger/data/security_scheme.rb +67 -0
  27. data/lib/ruby-swagger/data/tag.rb +24 -0
  28. data/lib/ruby-swagger/data/url.rb +26 -0
  29. data/lib/ruby-swagger/data/xml_object.rb +15 -0
  30. data/lib/ruby-swagger/grape/grape.rb +2 -0
  31. data/lib/ruby-swagger/grape/grape_config.rb +160 -0
  32. data/lib/ruby-swagger/grape/grape_presenter.rb +48 -0
  33. data/lib/ruby-swagger/grape/grape_template.rb +67 -0
  34. data/lib/ruby-swagger/grape/method.rb +295 -0
  35. data/lib/ruby-swagger/grape/param.rb +33 -0
  36. data/lib/ruby-swagger/grape/route_path.rb +37 -0
  37. data/lib/ruby-swagger/grape/routes.rb +52 -0
  38. data/lib/ruby-swagger/grape/type.rb +141 -0
  39. data/lib/ruby-swagger/io/comparable.rb +30 -0
  40. data/lib/ruby-swagger/io/definitions.rb +48 -0
  41. data/lib/ruby-swagger/io/file_system.rb +97 -0
  42. data/lib/ruby-swagger/io/paths.rb +55 -0
  43. data/lib/ruby-swagger/io/security.rb +45 -0
  44. data/lib/ruby-swagger/object.rb +67 -0
  45. data/lib/ruby-swagger/railtie.rb +7 -0
  46. data/lib/ruby-swagger/template.rb +29 -0
  47. data/lib/tasks/swagger.rake +125 -0
  48. metadata +176 -0
@@ -0,0 +1,26 @@
1
+ require 'addressable/uri'
2
+
3
+ module Swagger::Data
4
+ class Url
5
+
6
+ SCHEMES = %w(http https)
7
+
8
+ attr_reader :url
9
+
10
+ def initialize(url)
11
+ @url = url
12
+ end
13
+
14
+ def valid?
15
+ parsed = Addressable::URI.parse(url) or return false
16
+ SCHEMES.include?(parsed.scheme)
17
+ rescue Addressable::URI::InvalidURIError
18
+ false
19
+ end
20
+
21
+ def to_swagger
22
+ url
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ require 'ruby-swagger/object'
2
+
3
+ module Swagger::Data
4
+ class XMLObject < Swagger::Object #https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#xmlObject
5
+
6
+ attr_swagger :name, :namespace, :prefix, :attribute, :wrapped
7
+
8
+ def self.parse(xml_object)
9
+ return nil unless xml_object
10
+
11
+ Swagger::Data::XMLObject.new.bulk_set(xml_object)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ require 'ruby-swagger/grape/grape_presenter'
2
+ require 'ruby-swagger/grape/grape_config'
@@ -0,0 +1,160 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module Configuration
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ def api_desc(description, options = {}, &block)
11
+ default_api_options!(options)
12
+ block.call if block_given?
13
+ desc description, @api_options
14
+ end
15
+
16
+ def headers(headers_value)
17
+ raise ArgumentError.new("Grape::headers - unrecognized value #{headers_value} - allowed: Hash") unless headers_value.is_a?(Hash)
18
+
19
+ @api_options[:headers] = headers_value
20
+ end
21
+
22
+ def api_name(name_value)
23
+ raise ArgumentError.new("Grape::api_name - unrecognized value #{name_value} - allowed: String") unless name_value.is_a?(String)
24
+
25
+ @api_options[:api_name] = name_value
26
+ end
27
+
28
+ def deprecated(deprecation_value)
29
+ raise ArgumentError.new("Grape::deprecated - unrecognized value #{deprecation_value} - allowed: true|false") unless deprecation_value == true || deprecation_value == false
30
+
31
+ @api_options[:deprecated] = deprecation_value
32
+ end
33
+
34
+ def hidden(hidden_value)
35
+ raise ArgumentError.new("Grape::hidden - unrecognized value #{hidden_value} - allowed: true|false") unless hidden_value == true || hidden_value == false
36
+
37
+ @api_options[:hidden] = hidden_value
38
+ end
39
+
40
+ def detail(detail_value)
41
+ raise ArgumentError.new("Grape::detail - unrecognized value #{detail_value} - allowed: String") unless detail_value.is_a?(String)
42
+
43
+ @api_options[:detail] = detail_value
44
+ end
45
+
46
+ def scopes(scopes_value)
47
+ return if scopes_value.nil?
48
+
49
+ if scopes_value.is_a?(Array)
50
+ scopes_value.each do |scope|
51
+ raise ArgumentError.new("Grape::scopes - unrecognized scope #{scope_value}") unless scope.is_a?(String)
52
+ end
53
+
54
+ @api_options[:scopes] = scopes_value
55
+ elsif scopes_value.is_a?(String)
56
+ @api_options[:scopes] = [scopes_value]
57
+ else
58
+ raise ArgumentError.new("Grape::scopes - unrecognized value #{scopes_value} - scopes can either be a string or an array of strings")
59
+ end
60
+ end
61
+
62
+ def tags(new_tags)
63
+ raise ArgumentError.new("Grape::tags - unrecognized value #{new_tags} - tags can only be an array of strings or a string") unless new_tags.is_a?(Array) || new_tags.is_a?(String)
64
+
65
+ if new_tags.is_a?(String)
66
+ new_tags = [new_tags]
67
+ end
68
+
69
+ @api_options[:tags]= new_tags
70
+ end
71
+
72
+ def response(new_result, options = {})
73
+ raise ArgumentError.new("Grape::response - response can't be nil") unless new_result
74
+
75
+ response_obj = {entity: new_result}
76
+ response_obj[:root] = options[:root] || options['root']
77
+ response_obj[:headers] = options[:headers] || options['headers']
78
+ response_obj[:isArray] = options[:isArray] || options['isArray']
79
+
80
+ @api_options[:response]= response_obj
81
+ end
82
+
83
+ def errors(errors_value)
84
+ raise ArgumentError.new("Grape::errors - unrecognized value #{errors_value} - errors root must be a hash of errors") unless errors_value.is_a?(Hash)
85
+ @api_options[:errors]= errors_value
86
+ end
87
+
88
+ @@headers = {}
89
+ def default_headers(new_value)
90
+ @@headers = new_value
91
+ end
92
+
93
+ @@deprecated = false
94
+ def default_deprecated(new_value)
95
+ @@deprecated = new_value
96
+ end
97
+
98
+ @@hidden = false
99
+ def default_hidden(new_value)
100
+ @@hidden = new_value
101
+ end
102
+
103
+ @@scopes = nil
104
+ def default_scopes(new_value)
105
+ @@scopes = new_value
106
+ end
107
+
108
+ @@tags = []
109
+ def default_tags(new_value)
110
+ @@tags = new_value
111
+ end
112
+
113
+ @@result = nil
114
+ def default_result(new_value)
115
+ @@result = new_value
116
+ end
117
+
118
+ @@errors = nil
119
+ def default_errors(new_value)
120
+ @@errors = new_value
121
+ end
122
+
123
+ @@response_headers = nil
124
+ def default_response_headers(new_value)
125
+ @@response_headers = new_value
126
+ end
127
+
128
+ @@response_root = nil
129
+ def default_response_root(new_value)
130
+ @@response_root = new_value
131
+ end
132
+
133
+ @@response_entity = nil
134
+ def default_response_entity(new_value)
135
+ @@response_entity = new_value
136
+ end
137
+
138
+ def default_api_options!(options)
139
+ @api_options = {
140
+ headers: @@headers,
141
+ deprecated: @@deprecated,
142
+ hidden: @@hidden,
143
+ scopes: @@scopes,
144
+ tags: @@tags,
145
+ response: {
146
+ entity: @@response_entity,
147
+ root: @@response_root,
148
+ headers: @@response_headers,
149
+ isArray: false
150
+ },
151
+ errors: @@errors,
152
+ api_name: nil,
153
+ detail: ''
154
+ }.merge(options)
155
+ @description = ''
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,48 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module DSL
5
+ module InsideRoute
6
+ def api_present(*args)
7
+
8
+ args_list = args || []
9
+ options = {}
10
+
11
+ # Initialize the options hash - either by assigning to the current options for the method or with a new one
12
+ if args_list.count == 2
13
+
14
+ if args_list.last.kind_of?(Hash)
15
+ options = args_list.last
16
+ else
17
+ raise ArgumentError.new "The expected second argument for api_present is a Hash, but I got a #{args_list.last.class}"
18
+ end
19
+
20
+ elsif args_list.count == 1
21
+
22
+ #Initialize the option list
23
+ args_list << options
24
+
25
+ elsif args_list.count > 2 || args_list.count == 0
26
+ raise ArgumentError.new "Invalid number of arguments - got #{args_list.count}. expected 1 or 2 parameters"
27
+ end
28
+
29
+ # Setting the grape :with
30
+ if route.route_response.present? && route.route_response[:entity].present? &&!options[:with].present? && route.route_response[:entity].kind_of?(Class)
31
+ options[:with] = route.route_response[:entity]
32
+ end
33
+
34
+ # Setting the grape :root
35
+ if route.route_response.present? && route.route_response[:root].present? && !options[:root].present? && route.route_response[:root].kind_of?(String)
36
+ options[:root] = route.route_response[:root]
37
+ end
38
+
39
+ # Setting the :current_user extension
40
+ if defined?(current_user)
41
+ options[:current_user] = current_user
42
+ end
43
+
44
+ present *args_list
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,67 @@
1
+ require 'ruby-swagger/data/document'
2
+ require 'ruby-swagger/template'
3
+ require 'ruby-swagger/data/definitions'
4
+ require 'ruby-swagger/grape/routes'
5
+ require 'ruby-swagger/grape/type'
6
+ require 'ruby-swagger/data/security_scheme'
7
+ require 'ruby-swagger/data/security_definitions'
8
+
9
+ module Swagger::Grape
10
+ class Template
11
+
12
+ def self.generate(base_class)
13
+ swagger_doc = Swagger::Template.generate
14
+
15
+ routes = Swagger::Grape::Routes.new(base_class.routes)
16
+
17
+ swagger_doc.paths = routes.to_swagger
18
+ swagger_doc.definitions = Swagger::Data::Definitions.new
19
+
20
+ extract_all_types(routes.types).sort.each do |type|
21
+ grape_type = Swagger::Grape::Type.new(type)
22
+
23
+ swagger_doc.definitions.add_definition(type.to_s, grape_type.to_swagger(false))
24
+ end
25
+
26
+ if routes.scopes.present?
27
+ scheme = Swagger::Data::SecurityScheme.new
28
+ scheme.type = 'oauth2'
29
+ scheme.flow = 'accessCode'
30
+ scheme.authorizationUrl = 'https://'
31
+ scheme.tokenUrl = 'https://'
32
+ scopes = {}
33
+ routes.scopes.uniq.each do |scope|
34
+ scopes[scope] = ""
35
+ end
36
+ scheme.scopes = scopes
37
+
38
+ swagger_doc.securityDefinitions = Swagger::Data::SecurityDefinitions.new
39
+ swagger_doc.securityDefinitions.add_param("oauth2", scheme)
40
+ end
41
+
42
+ swagger_doc
43
+ end
44
+
45
+ def self.extract_all_types(types, all_types = [])
46
+ return all_types.uniq if types.length == 0
47
+
48
+ new_types = []
49
+
50
+ types.each do |type|
51
+ all_types << type.to_s unless all_types.include?(type.to_s)
52
+
53
+ grape_type = Swagger::Grape::Type.new(type)
54
+
55
+ grape_type.sub_types.each do |new_type|
56
+ unless all_types.include?(new_type.to_s)
57
+ new_types << new_type.to_s
58
+ all_types << new_type.to_s
59
+ end
60
+ end
61
+ end
62
+
63
+ extract_all_types(new_types, all_types)
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,295 @@
1
+ require 'ruby-swagger/data/operation'
2
+ require 'ruby-swagger/grape/type'
3
+
4
+ module Swagger::Grape
5
+ class Method
6
+
7
+ attr_reader :operation, :types, :scopes
8
+
9
+ def initialize(route_name, route)
10
+ @route_name = route_name
11
+ @route = route
12
+ @types = []
13
+ @scopes = []
14
+
15
+ new_operation
16
+ operation_params
17
+ operation_responses
18
+ operation_security
19
+
20
+ self
21
+ end
22
+
23
+ private
24
+
25
+ #generate the base of the operation
26
+ def new_operation
27
+ @operation = Swagger::Data::Operation.new
28
+ @operation.tags = grape_tags
29
+ @operation.operationId = @route.route_api_name if @route.route_api_name && @route.route_api_name.length > 0
30
+ @operation.summary = @route.route_description
31
+ @operation.description = (@route.route_detail && @route.route_detail.length > 0) ? @route.route_detail : @route.route_description
32
+ @operation.deprecated = @route.route_deprecated if @route.route_deprecated #grape extension
33
+
34
+ @operation
35
+ end
36
+
37
+ #extract all the parameters from the method definition (in path, url, body)
38
+ def operation_params
39
+ extract_params_and_types
40
+
41
+ @params.each do |param_name, parameter|
42
+ operation.add_parameter(parameter)
43
+ end
44
+ end
45
+
46
+ #extract the data about the response of the method
47
+ def operation_responses
48
+ @operation.responses = Swagger::Data::Responses.new
49
+
50
+ # Include all the possible errors in the response (store the types, they are documented separately)
51
+ (@route.route_errors || {}).each do |code, response|
52
+ error_response = {'description' => response['description'] || response[:description]}
53
+
54
+ if entity = (response[:entity] || response['entity'])
55
+ type = Object.const_get entity.to_s
56
+
57
+ error_response['schema'] = {}
58
+ error_response['schema']['$ref'] = "#/definitions/#{type.to_s}"
59
+
60
+ remember_type(type)
61
+ end
62
+
63
+ @operation.responses.add_response(code, Swagger::Data::Response.parse(error_response))
64
+ end
65
+
66
+ if @route.route_response.present? && @route.route_response[:entity].present?
67
+ rainbow_response = {'description' => 'Successful result of the operation'}
68
+
69
+ type = Swagger::Grape::Type.new(@route.route_response[:entity].to_s)
70
+ current_obj = rainbow_response['schema'] = {}
71
+ remember_type(@route.route_response[:entity])
72
+
73
+ # Include any response headers in the documentation of the response
74
+ if @route.route_response[:headers].present?
75
+ @route.route_response[:headers].each do |header_key, header_value|
76
+ next unless header_value.present?
77
+ rainbow_response['headers'] ||= {}
78
+
79
+ rainbow_response['headers'][header_key] = {
80
+ 'description'=> header_value['description'] || header_value[:description],
81
+ 'type'=> header_value['type'] || header_value[:type],
82
+ 'format'=> header_value['format'] || header_value[:format]
83
+ }
84
+ end
85
+ end
86
+
87
+ if @route.route_response[:root].present?
88
+ # A case where the response contains a single key in the response
89
+
90
+ if @route.route_response[:isArray] == true
91
+ # an array that starts from a key named root
92
+ rainbow_response['schema']['type'] = 'object'
93
+ rainbow_response['schema']['properties'] = {
94
+ @route.route_response[:root] => {
95
+ 'type' => 'array',
96
+ 'items' => type.to_swagger
97
+ }
98
+ }
99
+ else
100
+ rainbow_response['schema']['type'] = 'object'
101
+ rainbow_response['schema']['properties'] = {
102
+ @route.route_response[:root] => type.to_swagger
103
+ }
104
+ end
105
+
106
+ else
107
+
108
+ if @route.route_response[:isArray] == true
109
+ rainbow_response['schema']['type'] = 'array'
110
+ rainbow_response['schema']['items'] = type.to_swagger
111
+ else
112
+ rainbow_response['schema'] = type.to_swagger
113
+ end
114
+
115
+ end
116
+
117
+ @operation.responses.add_response('200', Swagger::Data::Response.parse(rainbow_response))
118
+ end
119
+
120
+ @operation.responses.add_response('default', Swagger::Data::Response.parse({'description' => 'Unexpected error'}))
121
+ end
122
+
123
+ def operation_security
124
+ if @route.route_scopes #grape extensions
125
+ security = Swagger::Data::SecurityRequirement.new
126
+ security.add_requirement('oauth2', @route.route_scopes)
127
+ @operation.security = [security]
128
+
129
+ @route.route_scopes.each do |scope|
130
+ @scopes << scope unless @scopes.include?(scope)
131
+ end
132
+ end
133
+ end
134
+
135
+ #extract the tags
136
+ def grape_tags
137
+ (@route.route_tags && !@route.route_tags.empty?) ? @route.route_tags : [@route_name.split('/')[1]]
138
+ end
139
+
140
+ def extract_params_and_types
141
+ @params = {}
142
+
143
+ header_params
144
+ path_params
145
+
146
+ case @route.route_method.downcase
147
+ when 'get'
148
+ query_params
149
+ when 'delete'
150
+ query_params
151
+ when 'post'
152
+ body_params
153
+ when 'put'
154
+ body_params
155
+ when 'patch'
156
+ body_params
157
+ when 'head'
158
+ raise ArgumentError.new("Don't know how to handle the http verb HEAD for #{@route_name}")
159
+ else
160
+ raise ArgumentError.new("Don't know how to handle the http verb #{@route.route_method} for #{@route_name}")
161
+ end
162
+
163
+ @params
164
+ end
165
+
166
+ def header_params
167
+ @params ||= {}
168
+
169
+ #include all the parameters that are in the headers
170
+ if @route.route_headers
171
+ @route.route_headers.each do |header_key, header_value|
172
+ @params[header_key] = {'name' => header_key,
173
+ 'in' => 'header',
174
+ 'required' => (header_value[:required] == true),
175
+ 'type' => 'string',
176
+ 'description' => header_value[:description]}
177
+ end
178
+ end
179
+
180
+ @params
181
+ end
182
+
183
+ def path_params
184
+ #include all the parameters that are in the path
185
+
186
+ @route_name.scan(/\{[a-zA-Z0-9\-\_]+\}/).each do |parameter| #scan all parameters in the url
187
+ param_name = parameter[1..parameter.length-2]
188
+ @params[param_name] = {'name' => param_name,
189
+ 'in' => 'path',
190
+ 'required' => true,
191
+ 'type' => 'string'}
192
+ end
193
+
194
+ end
195
+
196
+ def query_params
197
+ @route.route_params.each do |parameter|
198
+ next if @params[parameter.first.to_s]
199
+
200
+ swag_param = Swagger::Data::Parameter.from_grape(parameter)
201
+ next unless swag_param
202
+
203
+ swag_param.in = 'query'
204
+
205
+ @params[parameter.first.to_s] = swag_param
206
+ end
207
+ end
208
+
209
+ def body_params
210
+ #include all the parameters that are in the content-body
211
+ return unless @route.route_params && @route.route_params.length > 0
212
+
213
+ root_param = Swagger::Data::Parameter.parse({'name' => 'body',
214
+ 'in' => 'body',
215
+ 'description' => 'the content of the request',
216
+ 'schema' => {'type' => 'object', 'properties' => {}}})
217
+
218
+ #create the params schema
219
+ @route.route_params.each do |parameter|
220
+ param_name = parameter.first
221
+ param_value = parameter.last
222
+ schema = root_param.schema
223
+
224
+ next if @params.keys.include?(param_name)
225
+
226
+ if param_name.scan(/[0-9a-zA-Z_]+/).count == 1
227
+ #it's a simple parameter, adding it to the properties of the main object
228
+ converted_param = Swagger::Grape::Param.new(param_value)
229
+ schema.properties[param_name] = converted_param.to_swagger
230
+ required_parameter(schema, param_name, param_value)
231
+ remember_type(converted_param.type_definition) if converted_param.has_type_definition?
232
+ else
233
+ schema_with_subobjects(schema, param_name, parameter.last)
234
+ end
235
+
236
+ end
237
+
238
+ schema= root_param.schema
239
+ @params['body'] = root_param if !schema.properties.nil? && schema.properties.keys.length > 0
240
+ end
241
+
242
+ def required_parameter(schema, name, parameter)
243
+ return if parameter.nil? || parameter[:required].nil? || parameter[:required] == false
244
+
245
+ schema['required'] ||= []
246
+ schema['required'] << name
247
+ end
248
+
249
+ def schema_with_subobjects(schema, param_name, parameter)
250
+ path = param_name.scan(/[0-9a-zA-Z_]+/)
251
+ append_to = find_elem_in_schema(schema, path.dup)
252
+ converted_param = Swagger::Grape::Param.new(parameter)
253
+ append_to['properties'][path.last] = converted_param.to_swagger
254
+
255
+ remember_type(converted_param.type_definition) if converted_param.has_type_definition?
256
+
257
+ required_parameter(append_to, path.last, parameter)
258
+ end
259
+
260
+ def find_elem_in_schema(root, schema_path)
261
+ return root if schema_path.nil? || schema_path.empty?
262
+
263
+ next_elem = schema_path.shift
264
+
265
+ return root if root['properties'][next_elem].nil?
266
+
267
+ case root['properties'][next_elem]['type']
268
+ when 'array'
269
+ #to descend an array this must be an array of objects
270
+ root['properties'][next_elem]['items']['type'] = 'object'
271
+ root['properties'][next_elem]['items']['properties'] ||= {}
272
+
273
+ find_elem_in_schema(root['properties'][next_elem]['items'], schema_path)
274
+ when 'object'
275
+ find_elem_in_schema(root['properties'][next_elem], schema_path)
276
+ else
277
+ raise ArgumentError.new("Don't know how to handle the schema path #{schema_path.join('/')}")
278
+ end
279
+
280
+ end
281
+
282
+ # Store an object "type" seen on parameters or response types
283
+ def remember_type(type)
284
+ @types ||= []
285
+
286
+ return if %w(string integer boolean float array symbol virtus::attribute::boolean rack::multipart::uploadedfile date datetime).include?(type.to_s.downcase)
287
+
288
+ type = Object.const_get type.to_s
289
+ return if @types.include?(type.to_s)
290
+
291
+ @types << type.to_s
292
+ end
293
+
294
+ end
295
+ end