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