Lowercase model name in discovery document, like the real parameter name.
[arvados.git] / services / api / app / controllers / arvados / v1 / schema_controller.rb
1 class Arvados::V1::SchemaController < ApplicationController
2   skip_before_filter :find_object_by_uuid
3   skip_before_filter :require_auth_scope_all
4
5   def show
6     classes = Rails.cache.fetch 'arvados_v1_schema' do
7       Rails.application.eager_load!
8       classes = {}
9       ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |k|
10         classes[k] = k.columns.collect do |col|
11           if k.serialized_attributes.has_key? col.name
12             { name: col.name,
13               type: k.serialized_attributes[col.name].object_class.to_s }
14           else
15             { name: col.name,
16               type: col.type }
17           end
18         end
19       end
20       classes
21     end
22     render json: classes
23   end
24
25   def discovery_rest_description
26     discovery = Rails.cache.fetch 'arvados_v1_rest_discovery' do
27       Rails.application.eager_load!
28       discovery = {
29         kind: "discovery#restDescription",
30         discoveryVersion: "v1",
31         id: "arvados:v1",
32         name: "arvados",
33         version: "v1",
34         revision: "20131114",
35         generatedAt: Time.now.iso8601,
36         title: "Arvados API",
37         description: "The API to interact with Arvados.",
38         documentationLink: "https://redmine.clinicalfuture.com/projects/arvados/",
39         protocol: "rest",
40         baseUrl: root_url + "/arvados/v1/",
41         basePath: "/arvados/v1/",
42         rootUrl: root_url,
43         servicePath: "arvados/v1/",
44         batchPath: "batch",
45         parameters: {
46           alt: {
47             type: "string",
48             description: "Data format for the response.",
49             default: "json",
50             enum: [
51                    "json"
52                   ],
53             enumDescriptions: [
54                                "Responses with Content-Type of application/json"
55                               ],
56             location: "query"
57           },
58           fields: {
59             type: "string",
60             description: "Selector specifying which fields to include in a partial response.",
61             location: "query"
62           },
63           key: {
64             type: "string",
65             description: "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
66             location: "query"
67           },
68           oauth_token: {
69             type: "string",
70             description: "OAuth 2.0 token for the current user.",
71             location: "query"
72           }
73         },
74         auth: {
75           oauth2: {
76             scopes: {
77               "https://api.clinicalfuture.com/auth/arvados" => {
78                 description: "View and manage objects"
79               },
80               "https://api.clinicalfuture.com/auth/arvados.readonly" => {
81                 description: "View objects"
82               }
83             }
84           }
85         },
86         schemas: {},
87         resources: {}
88       }
89       
90       ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |k|
91         begin
92           ctl_class = "Arvados::V1::#{k.to_s.pluralize}Controller".constantize
93         rescue
94           # No controller -> no discovery.
95           next
96         end
97         object_properties = {}
98         k.columns.
99           select { |col| col.name != 'id' }.
100           collect do |col|
101           if k.serialized_attributes.has_key? col.name
102             object_properties[col.name] = {
103               type: k.serialized_attributes[col.name].object_class.to_s
104             }
105           else
106             object_properties[col.name] = {
107               type: col.type
108             }
109           end
110         end
111         discovery[:schemas][k.to_s + 'List'] = {
112           id: k.to_s + 'List',
113           description: k.to_s + ' list',
114           type: "object",
115           properties: {
116             kind: {
117               type: "string",
118               description: "Object type. Always arvados##{k.to_s.camelcase(:lower)}List.",
119               default: "arvados##{k.to_s.camelcase(:lower)}List"
120             },
121             etag: {
122               type: "string",
123               description: "List version."
124             },
125             items: {
126               type: "array",
127               description: "The list of #{k.to_s.pluralize}.",
128               items: {
129                 "$ref" => k.to_s
130               }
131             },
132             next_link: {
133               type: "string",
134               description: "A link to the next page of #{k.to_s.pluralize}."
135             },
136             next_page_token: {
137               type: "string",
138               description: "The page token for the next page of #{k.to_s.pluralize}."
139             },
140             selfLink: {
141               type: "string",
142               description: "A link back to this list."
143             }
144           }
145         }
146         discovery[:schemas][k.to_s] = {
147           id: k.to_s,
148           description: k.to_s,
149           type: "object",
150           properties: {
151             uuid: {
152               type: "string",
153               description: "Object ID."
154             },
155             etag: {
156               type: "string",
157               description: "Object version."
158             }
159           }.merge(object_properties)
160         }
161         discovery[:resources][k.to_s.underscore.pluralize] = {
162           methods: {
163             get: {
164               id: "arvados.#{k.to_s.underscore.pluralize}.get",
165               path: "#{k.to_s.underscore.pluralize}/{uuid}",
166               httpMethod: "GET",
167               description: "Gets a #{k.to_s}'s metadata by UUID.",
168               parameters: {
169                 uuid: {
170                   type: "string",
171                   description: "The UUID of the #{k.to_s} in question.",
172                   required: true,
173                   location: "path"
174                 }
175               },
176               parameterOrder: [
177                                "uuid"
178                               ],
179               response: {
180                 "$ref" => k.to_s
181               },
182               scopes: [
183                        "https://api.clinicalfuture.com/auth/arvados",
184                        "https://api.clinicalfuture.com/auth/arvados.readonly"
185                       ]
186             },
187             list: {
188               id: "arvados.#{k.to_s.underscore.pluralize}.list",
189               path: k.to_s.underscore.pluralize,
190               httpMethod: "GET",
191               description: "List #{k.to_s.underscore.pluralize}.",
192               parameters: {
193                 limit: {
194                   type: "integer",
195                   description: "Maximum number of #{k.to_s.underscore.pluralize} to return.",
196                   default: 100,
197                   format: "int32",
198                   minimum: 0,
199                   location: "query"
200                 },
201                 pageToken: {
202                   type: "string",
203                   description: "Page token.",
204                   location: "query"
205                 },
206                 q: {
207                   type: "string",
208                   description: "Query string for searching #{k.to_s.underscore.pluralize}.",
209                   location: "query"
210                 },
211                 where: {
212                   type: "object",
213                   description: "Conditions for filtering #{k.to_s.underscore.pluralize}.",
214                   location: "query"
215                 },
216                 order: {
217                   type: "string",
218                   description: "Order in which to return matching #{k.to_s.underscore.pluralize}.",
219                   location: "query"
220                 }
221               },
222               response: {
223                 "$ref" => "#{k.to_s}List"
224               },
225               scopes: [
226                        "https://api.clinicalfuture.com/auth/arvados",
227                        "https://api.clinicalfuture.com/auth/arvados.readonly"
228                       ]
229             },
230             create: {
231               id: "arvados.#{k.to_s.underscore.pluralize}.create",
232               path: "#{k.to_s.underscore.pluralize}",
233               httpMethod: "POST",
234               description: "Create a new #{k.to_s}.",
235               parameters: {
236                 k.to_s.underscore => {
237                   type: "object",
238                   required: false,
239                   location: "query",
240                   properties: object_properties
241                 }
242               },
243               request: {
244                 required: false,
245                 properties: {
246                   k.to_s.underscore => {
247                     "$ref" => k.to_s
248                   }
249                 }
250               },
251               response: {
252                 "$ref" => k.to_s
253               },
254               scopes: [
255                        "https://api.clinicalfuture.com/auth/arvados"
256                       ]
257             },
258             update: {
259               id: "arvados.#{k.to_s.underscore.pluralize}.update",
260               path: "#{k.to_s.underscore.pluralize}/{uuid}",
261               httpMethod: "PUT",
262               description: "Update attributes of an existing #{k.to_s}.",
263               parameters: {
264                 uuid: {
265                   type: "string",
266                   description: "The UUID of the #{k.to_s} in question.",
267                   required: true,
268                   location: "path"
269                 },
270                 k.to_s.underscore => {
271                   type: "object",
272                   required: false,
273                   location: "query",
274                   properties: object_properties
275                 }
276               },
277               request: {
278                 required: false,
279                 properties: {
280                   k.to_s.underscore => {
281                     "$ref" => k.to_s
282                   }
283                 }
284               },
285               response: {
286                 "$ref" => k.to_s
287               },
288               scopes: [
289                        "https://api.clinicalfuture.com/auth/arvados"
290                       ]
291             },
292             delete: {
293               id: "arvados.#{k.to_s.underscore.pluralize}.delete",
294               path: "#{k.to_s.underscore.pluralize}/{uuid}",
295               httpMethod: "DELETE",
296               description: "Delete an existing #{k.to_s}.",
297               parameters: {
298                 uuid: {
299                   type: "string",
300                   description: "The UUID of the #{k.to_s} in question.",
301                   required: true,
302                   location: "path"
303                 }
304               },
305               response: {
306                 "$ref" => k.to_s
307               },
308               scopes: [
309                        "https://api.clinicalfuture.com/auth/arvados"
310                       ]
311             }
312           }
313         }
314         # Check for Rails routes that don't match the usual actions
315         # listed above
316         d_methods = discovery[:resources][k.to_s.underscore.pluralize][:methods]
317         Rails.application.routes.routes.each do |route|
318           action = route.defaults[:action]
319           httpMethod = ['GET', 'POST', 'PUT', 'DELETE'].map { |method|
320             method if route.verb.match(method)
321           }.compact.first
322           if httpMethod and
323               route.defaults[:controller] == 'arvados/v1/' + k.to_s.underscore.pluralize and
324               !d_methods[action.to_sym] and
325               ctl_class.action_methods.include? action
326             method = {
327               id: "arvados.#{k.to_s.underscore.pluralize}.#{action}",
328               path: route.path.spec.to_s.sub('/arvados/v1/','').sub('(.:format)','').sub(/:(uu)?id/,'{uuid}'),
329               httpMethod: httpMethod,
330               description: "#{route.defaults[:action]} #{k.to_s.underscore.pluralize}",
331               parameters: {},
332               response: {
333                 "$ref" => (action == 'index' ? "#{k.to_s}List" : k.to_s)
334               },
335               scopes: [
336                        "https://api.clinicalfuture.com/auth/arvados"
337                       ]
338             }
339             route.segment_keys.each do |key|
340               if key != :format
341                 key = :uuid if key == :id
342                 method[:parameters][key] = {
343                   type: "string",
344                   description: "",
345                   required: true,
346                   location: "path"
347                 }
348               end
349             end
350             if ctl_class.respond_to? "_#{action}_requires_parameters".to_sym
351               ctl_class.send("_#{action}_requires_parameters".to_sym).each do |k, v|
352                 if v.is_a? Hash
353                   method[:parameters][k] = v
354                 else
355                   method[:parameters][k] = {}
356                 end
357                 method[:parameters][k][:type] ||= 'string'
358                 method[:parameters][k][:description] ||= ''
359                 method[:parameters][k][:location] = (route.segment_keys.include?(k) ? 'path' : 'query')
360                 if method[:parameters][k][:required].nil?
361                   method[:parameters][k][:required] = v != false
362                 end
363               end
364             end
365             d_methods[route.defaults[:action].to_sym] = method
366           end
367         end
368       end
369       discovery
370     end
371     render json: discovery
372   end
373 end