b628671b8e2c56a3232bab4ebade38fe45324c21
[arvados.git] / lib / google / api_client / discovery / schema.rb
1 # Copyright 2010 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15
16 require 'time'
17 require 'json'
18 require 'addressable/uri'
19 require 'addressable/template'
20
21 require 'google/inflection'
22 require 'google/api_client/errors'
23
24 module Google
25   class APIClient
26     class Schema
27       def initialize(api, api_name, api_version, discovery_document)
28         # This constructor is super-long, but hard to break up due to the
29         # unavoidable dependence on closures and execution context.
30         @api = api
31         @discovery_document = discovery_document
32         api_name_string =
33           Google::INFLECTOR.camelize(api_name)
34         api_version_string =
35           Google::INFLECTOR.camelize(api_version).gsub('.', '_')
36         @schema_name = @discovery_document['id']
37         if Google::APIClient::Schema.const_defined?(api_name_string)
38           @api_name = Google::APIClient::Schema.const_get(api_name_string)
39         else
40           @api_name = Google::APIClient::Schema.const_set(
41             api_name_string, Class.new
42           )
43         end
44         if @api_name.const_defined?(api_version_string)
45           @api_version = @api_name.const_get(api_version_string)
46         else
47           @api_version = @api_name.const_set(api_version_string, Class.new)
48         end
49         if @api_version.const_defined?(@schema_name)
50           @schema_class = @api_version.const_get(@schema_name)
51         else
52           schema = self
53           @schema_class = @api_version.const_set(
54             @schema_name,
55             Class.new(APIObject) do |klass|
56               discovery_document['properties'].each do |(k, v)|
57                 property_name = Google::INFLECTOR.underscore(k)
58                 define_method(:schema) { schema }
59                 define_method(property_name + '_property') do
60                   v
61                 end
62                 case v['type']
63                 when 'string'
64                   define_string_property(property_name, k, v)
65                 when 'boolean'
66                   define_boolean_property(property_name, k, v)
67                 when 'number'
68                   define_number_property(property_name, k, v)
69                 when 'object'
70                   define_object_property(property_name, k, v)
71                 else
72                   # Either type 'any' or we don't know what this is,
73                   # default to anything goes.
74                   define_any_property(property_name, k, v)
75                 end
76               end
77             end
78           )
79         end
80       end
81
82       def schema_name
83         return @schema_name
84       end
85
86       def schema_class
87         return @schema_class
88       end
89
90       ##
91       # Returns a <code>String</code> representation of the resource's state.
92       #
93       # @return [String] The resource's state, as a <code>String</code>.
94       def inspect
95         sprintf(
96           "#<%s:%#0x CLASS:%s>",
97           self.class.to_s, self.object_id, self.schema_class.name
98         )
99       end
100     end
101
102     class APIObject
103       def self.define_string_property(property_name, key, schema)
104         define_method(property_name) do
105           self[key] ||= schema['default']
106           if schema['format'] == 'byte' && self[key] != nil
107             Base64.decode64(self[key])
108           elsif schema['format'] == 'date-time' && self[key] != nil
109             Time.parse(self[key])
110           elsif schema['format'] =~ /^u?int(32|64)$/ && self[key] != nil
111             self[key].to_i
112           else
113             self[key]
114           end
115         end
116         define_method(property_name + '=') do |value|
117           if schema['format'] == 'byte'
118             self[key] = Base64.encode64(value)
119           elsif schema['format'] == 'date-time'
120             if value.respond_to?(:to_str)
121               value = Time.parse(value.to_str)
122             elsif !value.respond_to?(:xmlschema)
123               raise TypeError,
124                 "Could not obtain RFC 3339 timestamp from #{value.class}."
125             end
126             self[key] = value.xmlschema
127           elsif schema['format'] =~ /^u?int(32|64)$/
128             self[key] = value.to_s
129           elsif value.respond_to?(:to_str)
130             self[key] = value.to_str
131           elsif value.kind_of?(Symbol)
132             self[key] = value.to_s
133           else
134             raise TypeError,
135               "Expected String or Symbol, got #{value.class}."
136           end
137         end
138       end
139
140       def self.define_boolean_property(property_name, key, schema)
141         define_method(property_name) do
142           self[key] ||= schema['default']
143           case self[key].to_s.downcase
144           when 'true', 'yes', 'y', 'on', '1'
145             true
146           when 'false', 'no', 'n', 'off', '0'
147             false
148           when 'nil', 'null'
149             nil
150           else
151             raise TypeError,
152               "Expected boolean, got #{self[key].class}."
153           end
154         end
155         define_method(property_name + '=') do |value|
156           case value.to_s.downcase
157           when 'true', 'yes', 'y', 'on', '1'
158             self[key] = true
159           when 'false', 'no', 'n', 'off', '0'
160             self[key] = false
161           when 'nil', 'null'
162             self[key] = nil
163           else
164             raise TypeError, "Expected boolean, got #{value.class}."
165           end
166         end
167       end
168
169       def self.define_number_property(property_name, key, schema)
170         define_method(property_name) do
171           self[key] ||= schema['default']
172           if self[key] != nil && !self[key].respond_to?(:to_f)
173             raise TypeError,
174               "Expected Float, got #{self[key].class}."
175           elsif self[key] != nil && self[key].respond_to?(:to_f)
176             self[key].to_f
177           else
178             self[key]
179           end
180         end
181         define_method(property_name + '=') do |value|
182           if value == nil
183             self[key] = value
184           else
185             case schema['format']
186             when 'double', 'float'
187               if value.respond_to?(:to_f)
188                 self[key] = value.to_f
189               else
190                 raise TypeError,
191                   "Expected String or Symbol, got #{value.class}."
192               end
193             else
194               raise TypeError,
195                 "Unexpected type format for number: #{schema['format']}."
196             end
197           end
198         end
199       end
200
201       def self.define_object_property(property_name, key, schema)
202         # TODO(bobaman):
203         # Do we treat this differently from any?
204         self.define_any_property(property_name, key, schema)
205       end
206
207       def self.define_any_property(property_name, key, schema)
208         define_method(property_name) do
209           self[k] || v['default']
210         end
211         define_method(property_name + '=') do |value|
212           self[k] = value
213         end
214       end
215
216       def initialize(data)
217         @data = data
218       end
219
220       def [](key)
221         return @data[key]
222       end
223
224       def []=(key, value)
225         return @data[key] = value
226       end
227     end
228   end
229 end