Check HTTP status before using API responses.
[arvados.git] / apps / workbench / app / models / arvados_api_client.rb
1 require 'httpclient'
2 require 'thread'
3
4 class ArvadosApiClient
5   class NotLoggedInException < StandardError
6   end
7   class InvalidApiResponseException < StandardError
8   end
9
10   @@client_mtx = Mutex.new
11   @@api_client = nil
12
13   def api(resources_kind, action, data=nil)
14     @@client_mtx.synchronize do
15       if not @@api_client 
16         @@api_client = HTTPClient.new
17         if Rails.configuration.arvados_insecure_https
18           @@api_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
19         end
20       end
21     end
22
23     api_token = Thread.current[:arvados_api_token]
24     api_token ||= ''
25
26     resources_kind = class_kind(resources_kind).pluralize if resources_kind.is_a? Class
27     url = "#{self.arvados_v1_base}/#{resources_kind}#{action}"
28
29     query = {"api_token" => api_token}
30     if !data.nil?
31       data.each do |k,v|
32         if v.is_a? String or v.nil?
33           query[k] = v
34         elsif v == true
35           query[k] = 1
36         elsif v == false
37           query[k] = 0
38         else
39           query[k] = JSON.dump(v)
40         end
41       end
42     else
43       query["_method"] = "GET"
44     end 
45     
46     header = {"Accept" => "application/json"}
47
48     msg = @@api_client.post(url, 
49                             query,
50                             header: header)
51
52     if msg.status_code == 401
53       raise NotLoggedInException.new
54     end
55
56     json = msg.content
57     
58     begin
59       resp = Oj.load(json, :symbol_keys => true)
60     rescue Oj::ParseError
61       raise InvalidApiResponseException.new json
62     end
63     if not resp.is_a? Hash
64       raise InvalidApiResponseException.new json
65     end
66     if msg.status_code != 200
67       errors = resp[:errors]
68       errors = errors.join("\n\n") if errors.is_a? Array
69       raise "API error #{msg.status_code}:\n\n#{errors}\n"
70     end
71     resp
72   end
73
74   def unpack_api_response(j, kind=nil)
75     if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
76       ary = j[:items].collect { |x| unpack_api_response x, j[:kind] }
77       if j[:items_available]
78         (class << ary; self; end).class_eval { attr_accessor :items_available }
79         ary.items_available = j[:items_available]
80       end
81       ary
82     elsif j.is_a? Hash and (kind || j[:kind])
83       oclass = self.kind_class(kind || j[:kind])
84       if oclass
85         j.keys.each do |k|
86           childkind = j["#{k.to_s}_kind".to_sym]
87           if childkind
88             j[k] = self.unpack_api_response(j[k], childkind)
89           end
90         end
91         oclass.new.private_reload(j)
92       else
93         j
94       end
95     else
96       j
97     end
98   end
99
100   def arvados_login_url(params={})
101     if Rails.configuration.respond_to? :arvados_login_base
102       uri = Rails.configuration.arvados_login_base
103     else
104       uri = self.arvados_v1_base.sub(%r{/arvados/v\d+.*}, '/login')
105     end
106     if params.size > 0
107       uri += '?' << params.collect { |k,v|
108         CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s)
109       }.join('&')
110     end
111   end
112
113   def arvados_logout_url(params={})
114     arvados_login_url(params).sub('/login','/logout')
115   end
116
117   def arvados_v1_base
118     Rails.configuration.arvados_v1_base
119   end
120
121   def arvados_schema
122     @arvados_schema ||= api 'schema', ''
123   end
124
125   def kind_class(kind)
126     kind.match(/^arvados\#(.+?)(_list|List)?$/)[1].pluralize.classify.constantize rescue nil
127   end
128
129   def class_kind(resource_class)
130     resource_class.to_s.underscore
131   end
132 end