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