1 # Copyright 2010 Google Inc.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
19 require 'addressable/uri'
20 require 'addressable/template'
22 require 'google/inflection'
23 require 'google/api_client/errors'
28 def self.parse(api, schema_data)
29 # This method is super-long, but hard to break up due to the
30 # unavoidable dependence on closures and execution context.
31 schema_name = schema_data['id']
35 Google::INFLECTOR.camelize(api.name)
37 Google::INFLECTOR.camelize(api.version).gsub('.', '_')
38 if Google::APIClient::Schema.const_defined?(api_name_string)
39 api_name = Google::APIClient::Schema.const_get(api_name_string)
41 api_name = Google::APIClient::Schema.const_set(
42 api_name_string, Module.new
45 if api_name.const_defined?(api_version_string)
46 api_version = api_name.const_get(api_version_string)
48 api_version = api_name.const_set(api_version_string, Module.new)
50 if api_version.const_defined?(schema_name)
51 schema_class = api_version.const_get(schema_name)
55 # It's possible the schema has already been defined. If so, don't
56 # redefine it. This means that reloading a schema which has already
57 # been loaded into memory is not possible.
60 schema_class = Class.new(APIObject) do |klass|
62 define_method('schema') do
65 (schema_data['properties'] || []).each do |(k, v)|
66 property_name = Google::INFLECTOR.underscore(k)
67 properties << property_name.to_sym
68 define_method(:schema) { schema }
69 define_method(property_name + '_schema') do
72 define_method(property_name + '_description') do
77 define_string_property(api, property_name, k, v)
79 define_boolean_property(api, property_name, k, v)
81 define_number_property(api, property_name, k, v)
83 define_array_property(api, property_name, k, v)
85 define_object_property(api, property_name, k, v)
87 # Either type 'any' or we don't know what this is,
88 # default to anything goes.
89 define_any_property(api, property_name, k, v)
93 define_method('properties') do
98 api_version.const_set(schema_name, schema_class)
106 def self.define_string_property(api, property_name, key, schema_data)
107 define_method(property_name) do
108 self[key] ||= schema_data['default']
109 if schema_data['format'] == 'byte' && self[key] != nil
110 Base64.decode64(self[key])
111 elsif schema_data['format'] == 'date-time' && self[key] != nil
112 Time.parse(self[key])
113 elsif schema_data['format'] =~ /^u?int(32|64)$/ && self[key] != nil
119 define_method(property_name + '=') do |value|
120 if schema_data['format'] == 'byte'
121 self[key] = Base64.encode64(value)
122 elsif schema_data['format'] == 'date-time'
123 if value.respond_to?(:to_str)
124 value = Time.parse(value.to_str)
125 elsif !value.respond_to?(:xmlschema)
127 "Could not obtain RFC 3339 timestamp from #{value.class}."
129 self[key] = value.xmlschema
130 elsif schema_data['format'] =~ /^u?int(32|64)$/
131 self[key] = value.to_s
132 elsif value.respond_to?(:to_str)
133 self[key] = value.to_str
134 elsif value.kind_of?(Symbol)
135 self[key] = value.to_s
138 "Expected String or Symbol, got #{value.class}."
143 def self.define_boolean_property(api, property_name, key, schema_data)
144 define_method(property_name) do
145 self[key] ||= schema_data['default']
146 case self[key].to_s.downcase
147 when 'true', 'yes', 'y', 'on', '1'
149 when 'false', 'no', 'n', 'off', '0'
155 "Expected boolean, got #{self[key].class}."
158 define_method(property_name + '=') do |value|
159 case value.to_s.downcase
160 when 'true', 'yes', 'y', 'on', '1'
162 when 'false', 'no', 'n', 'off', '0'
167 raise TypeError, "Expected boolean, got #{value.class}."
172 def self.define_number_property(api, property_name, key, schema_data)
173 define_method(property_name) do
174 self[key] ||= schema_data['default']
175 if self[key] != nil && !self[key].respond_to?(:to_f)
177 "Expected Float, got #{self[key].class}."
178 elsif self[key] != nil && self[key].respond_to?(:to_f)
184 define_method(property_name + '=') do |value|
188 case schema_data['format']
189 when 'double', 'float'
190 if value.respond_to?(:to_f)
191 self[key] = value.to_f
194 "Expected String or Symbol, got #{value.class}."
198 "Unexpected type format for number: #{schema_data['format']}."
204 def self.define_array_property(api, property_name, key, schema_data)
205 define_method(property_name) do
206 # The default value of an empty Array obviates a mutator method.
208 array = if self[key] != nil && !self[key].respond_to?(:to_ary)
210 "Expected Array, got #{self[key].class}."
214 if schema_data['items'] && schema_data['items']['$ref']
215 schema_name = schema_data['items']['$ref']
216 if api.schemas[schema_name]
217 schema_class = api.schemas[schema_name]
219 schema_class.new(item)
223 "Could not find schema '#{schema_name}' in API '#{api.id}'."
230 def self.define_object_property(api, property_name, key, schema_data)
231 # TODO finish this up...
232 schema = Schema.parse(api, schema_data)
233 define_method(property_name) do
234 self[key] ||= v['default']
235 schema.new(self[key])
239 def self.define_any_property(api, property_name, key, schema_data)
240 define_method(property_name) do
241 self[key] ||= v['default']
243 define_method(property_name + '=') do |value|
257 return @data[key] = value