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