KayakoClient 0.0.1b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/lib/kayako_client.rb +36 -0
  2. data/lib/kayako_client/base.rb +98 -0
  3. data/lib/kayako_client/department.rb +31 -0
  4. data/lib/kayako_client/http/http.rb +217 -0
  5. data/lib/kayako_client/http/http_backend.rb +31 -0
  6. data/lib/kayako_client/http/http_client.rb +87 -0
  7. data/lib/kayako_client/http/http_response.rb +61 -0
  8. data/lib/kayako_client/http/net_http.rb +109 -0
  9. data/lib/kayako_client/mixins/api.rb +299 -0
  10. data/lib/kayako_client/mixins/attachment.rb +69 -0
  11. data/lib/kayako_client/mixins/authentication.rb +34 -0
  12. data/lib/kayako_client/mixins/client.rb +308 -0
  13. data/lib/kayako_client/mixins/logger.rb +29 -0
  14. data/lib/kayako_client/mixins/object.rb +456 -0
  15. data/lib/kayako_client/mixins/post_client.rb +42 -0
  16. data/lib/kayako_client/mixins/staff_visibility_api.rb +9 -0
  17. data/lib/kayako_client/mixins/ticket_api.rb +55 -0
  18. data/lib/kayako_client/mixins/ticket_client.rb +135 -0
  19. data/lib/kayako_client/mixins/user_visibility_api.rb +9 -0
  20. data/lib/kayako_client/staff.rb +25 -0
  21. data/lib/kayako_client/staff_group.rb +9 -0
  22. data/lib/kayako_client/ticket.rb +241 -0
  23. data/lib/kayako_client/ticket_attachment.rb +42 -0
  24. data/lib/kayako_client/ticket_count.rb +135 -0
  25. data/lib/kayako_client/ticket_custom_field.rb +110 -0
  26. data/lib/kayako_client/ticket_note.rb +105 -0
  27. data/lib/kayako_client/ticket_post.rb +61 -0
  28. data/lib/kayako_client/ticket_priority.rb +24 -0
  29. data/lib/kayako_client/ticket_status.rb +31 -0
  30. data/lib/kayako_client/ticket_time_track.rb +27 -0
  31. data/lib/kayako_client/ticket_type.rb +26 -0
  32. data/lib/kayako_client/user.rb +61 -0
  33. data/lib/kayako_client/user_group.rb +12 -0
  34. data/lib/kayako_client/user_organization.rb +23 -0
  35. data/lib/kayako_client/xml/lib_xml.rb +86 -0
  36. data/lib/kayako_client/xml/rexml_document.rb +77 -0
  37. data/lib/kayako_client/xml/xml.rb +63 -0
  38. data/lib/kayako_client/xml/xml_backend.rb +42 -0
  39. metadata +105 -0
@@ -0,0 +1,69 @@
1
+ require 'tempfile'
2
+
3
+ module KayakoClient
4
+ module Attachment
5
+
6
+ def initialize(*args)
7
+ if args.last.is_a?(Hash) && args.last[:file]
8
+ self.file = args.last.delete(:file)
9
+ end
10
+ super(*args)
11
+ end
12
+
13
+ def file=(arg)
14
+ unless self.class.properties[:contents] && self.class.properties[:file_name]
15
+ raise RuntimeError, "missing :contents and/or :file_name properties"
16
+ end
17
+ raise ArgumentError, "object properties are read-only" if self.class.embedded?
18
+ if self.class.options[:contents] && self.class.options[:contents][:readonly]
19
+ raise ArgumentError, "property :contents is read-only"
20
+ end
21
+ if self.class.options[:contents] && self.class.options[:contents][:new] && !new?
22
+ raise ArgumentError, "property :contents cannot be changed"
23
+ end
24
+ case arg
25
+ when File, Tempfile
26
+ arg.rewind
27
+ arg.binmode
28
+ @contents = arg.read
29
+ when String
30
+ raise ArgumentError, "file path can't be empty" if arg.empty?
31
+ @contents = File.open(arg, 'rb') do |f|
32
+ f.read
33
+ end
34
+ else
35
+ raise ArgumentError, "invalid argument must be either File or path"
36
+ end
37
+ changes(:contents)
38
+ if !self.class.options[:file_name] ||
39
+ (!self.class.options[:contents][:readonly] && (!self.class.options[:contents][:new] || new?))
40
+ case arg
41
+ when File
42
+ @file_name = File.basename(arg.path)
43
+ when String
44
+ @file_name = File.basename(arg)
45
+ end
46
+ changes(:file_name)
47
+ end
48
+ end
49
+
50
+ def file
51
+ unless self.class.properties[:contents] && self.class.properties[:file_name]
52
+ raise RuntimeError, "missing :contents and/or :file_name properties"
53
+ end
54
+ if defined?(@file)
55
+ @file
56
+ elsif contents
57
+ raise RuntimeError, "not a remote file" unless id && !new?
58
+ @file = Tempfile.new(file_name.split('.').first || 'kayako_attachment')
59
+ @file.write(contents)
60
+ @file.flush
61
+ @file.rewind
62
+ @file
63
+ else
64
+ nil
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,34 @@
1
+ require 'base64'
2
+
3
+ module KayakoClient
4
+ module Authentication
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def salt(count = 32)
13
+ pass = ''
14
+ chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
15
+ count.times do |i|
16
+ pass << chars[rand(chars.size - 1)]
17
+ end
18
+ pass
19
+ end
20
+
21
+ def signature(salt, secret = nil)
22
+ begin
23
+ require 'openssl'
24
+ hash = OpenSSL::HMAC::digest(OpenSSL::Digest::SHA256.new, secret || secret_key, salt)
25
+ rescue LoadError, NameError
26
+ hash = HMAC::sha256(secret || secret_key, salt)
27
+ end
28
+ Base64.encode64(hash).strip
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,308 @@
1
+ module KayakoClient
2
+ module Client
3
+
4
+ def departments(options = {})
5
+ KayakoClient::Department.all(options.merge(inherited_options))
6
+ end
7
+
8
+ def get_department(id = :all, options = {})
9
+ KayakoClient::Department.get(id, options.merge(inherited_options))
10
+ end
11
+
12
+ alias_method :find_department, :get_department
13
+
14
+ def post_department(options = {})
15
+ KayakoClient::Department.post(options.merge(inherited_options))
16
+ end
17
+
18
+ alias_method :create_department, :post_department
19
+
20
+ def delete_department(id, options = {})
21
+ KayakoClient::Department.delete(id, options.merge(inherited_options))
22
+ end
23
+
24
+ alias_method :destroy_department, :delete_department
25
+
26
+
27
+ def staff(options = {})
28
+ KayakoClient::Staff.all(options.merge(inherited_options))
29
+ end
30
+
31
+ alias_method :staff_users, :staff
32
+
33
+ def get_staff(id = :all, options = {})
34
+ KayakoClient::Staff.get(id, options.merge(inherited_options))
35
+ end
36
+
37
+ alias_method :find_staff, :get_staff
38
+
39
+ def post_staff(options = {})
40
+ KayakoClient::Staff.post(options.merge(inherited_options))
41
+ end
42
+
43
+ alias_method :create_staff, :post_staff
44
+
45
+ def delete_staff(id, options = {})
46
+ KayakoClient::Staff.delete(id, options.merge(inherited_options))
47
+ end
48
+
49
+ alias_method :destroy_staff, :delete_staff
50
+
51
+
52
+ def staff_groups(options = {})
53
+ KayakoClient::StaffGroup.all(options.merge(inherited_options))
54
+ end
55
+
56
+ def get_staff_group(id = :all, options = {})
57
+ KayakoClient::StaffGroup.get(id, options.merge(inherited_options))
58
+ end
59
+
60
+ alias_method :find_staff_group, :get_staff_group
61
+
62
+ def post_staff_group(options = {})
63
+ KayakoClient::StaffGroup.post(options.merge(inherited_options))
64
+ end
65
+
66
+ alias_method :create_staff_group, :post_staff_group
67
+
68
+ def delete_staff_group(id, options = {})
69
+ KayakoClient::StaffGroup.delete(id, options.merge(inherited_options))
70
+ end
71
+
72
+ alias_method :destroy_staff_group, :delete_staff_group
73
+
74
+
75
+ def tickets(*args)
76
+ options = args.last.is_a?(Hash) ? args.pop : {}
77
+ args << options.merge(inherited_options)
78
+ KayakoClient::Ticket.all(*args)
79
+ end
80
+
81
+ def get_ticket(id = :all, options = {})
82
+ KayakoClient::Ticket.get(id, options.merge(inherited_options))
83
+ end
84
+
85
+ alias_method :find_ticket, :get_ticket
86
+
87
+ def ticket_search(query, flags = KayakoClient::Ticket::SEARCH_CONTENTS, options = {})
88
+ KayakoClient::Ticket.search(query, flags, options.merge(inherited_options))
89
+ end
90
+
91
+ def post_ticket(options = {})
92
+ KayakoClient::Ticket.post(options.merge(inherited_options))
93
+ end
94
+
95
+ alias_method :create_ticket, :post_ticket
96
+
97
+ def delete_ticket(id, options = {})
98
+ KayakoClient::Ticket.delete(id, options.merge(inherited_options))
99
+ end
100
+
101
+ alias_method :destroy_ticket, :delete_ticket
102
+
103
+
104
+ def ticket_attachments(ticket, options = {})
105
+ KayakoClient::TicketAttachment.all(ticket, options.merge(inherited_options))
106
+ end
107
+
108
+ def get_ticket_attachment(ticket, id, options = {})
109
+ KayakoClient::TicketAttachment.get(ticket, id, options.merge(inherited_options))
110
+ end
111
+
112
+ alias_method :find_ticket_attachment, :get_ticket_attachment
113
+
114
+ def post_ticket_attachment(options = {})
115
+ KayakoClient::TicketAttachment.post(options.merge(inherited_options))
116
+ end
117
+
118
+ alias_method :create_ticket_attachment, :post_ticket_attachment
119
+
120
+ def delete_ticket_attachment(ticket, id, options = {})
121
+ KayakoClient::TicketAttachment.delete(ticket, id, options.merge(inherited_options))
122
+ end
123
+
124
+ alias_method :destroy_ticket_attachment, :delete_ticket_attachment
125
+
126
+
127
+ def ticket_count(options = {})
128
+ KayakoClient::TicketCount.get(options.merge(inherited_options))
129
+ end
130
+
131
+
132
+ def ticket_custom_fields(ticket, options = {})
133
+ KayakoClient::TicketCustomField.get(ticket, options.merge(inherited_options))
134
+ end
135
+
136
+
137
+ def ticket_notes(ticket, options = {})
138
+ KayakoClient::TicketNote.all(ticket, options.merge(inherited_options))
139
+ end
140
+
141
+ def get_ticket_note(ticket, id, options = {})
142
+ KayakoClient::TicketNote.get(ticket, id, options.merge(inherited_options))
143
+ end
144
+
145
+ alias_method :find_ticket_note, :get_ticket_note
146
+
147
+ def post_ticket_note(options = {})
148
+ KayakoClient::TicketNote.post(options.merge(inherited_options))
149
+ end
150
+
151
+ alias_method :create_ticket_note, :post_ticket_note
152
+
153
+ def delete_ticket_note(ticket, id, options = {})
154
+ KayakoClient::TicketNote.delete(ticket, id, options.merge(inherited_options))
155
+ end
156
+
157
+ alias_method :destroy_ticket_note, :delete_ticket_note
158
+
159
+
160
+ def ticket_posts(ticket, options = {})
161
+ KayakoClient::TicketPost.all(ticket, options.merge(inherited_options))
162
+ end
163
+
164
+ def get_ticket_post(ticket, id, options = {})
165
+ KayakoClient::TicketPost.get(ticket, id, options.merge(inherited_options))
166
+ end
167
+
168
+ alias_method :find_ticket_post, :get_ticket_post
169
+
170
+ def post_ticket_post(options = {})
171
+ KayakoClient::TicketPost.post(options.merge(inherited_options))
172
+ end
173
+
174
+ alias_method :create_ticket_post, :post_ticket_post
175
+
176
+ def delete_ticket_post(ticket, id, options = {})
177
+ KayakoClient::TicketPost.delete(ticket, id, options.merge(inherited_options))
178
+ end
179
+
180
+ alias_method :destroy_ticket_post, :delete_ticket_post
181
+
182
+
183
+ def ticket_priorities(options = {})
184
+ KayakoClient::TicketPriority.all(options.merge(inherited_options))
185
+ end
186
+
187
+ def get_ticket_priority(id = :all, options = {})
188
+ KayakoClient::TicketPriority.get(id, options.merge(inherited_options))
189
+ end
190
+
191
+ alias_method :find_ticket_priority, :get_ticket_priority
192
+
193
+
194
+ def ticket_statuses(options = {})
195
+ KayakoClient::TicketStatus.all(options.merge(inherited_options))
196
+ end
197
+
198
+ def get_ticket_status(id = :all, options = {})
199
+ KayakoClient::TicketStatus.get(id, options.merge(inherited_options))
200
+ end
201
+
202
+ alias_method :find_ticket_status, :get_ticket_status
203
+
204
+
205
+ def ticket_time_tracks(ticket, options = {})
206
+ KayakoClient::TicketTimeTrack.all(ticket, options.merge(inherited_options))
207
+ end
208
+
209
+ def get_ticket_time_track(ticket, id, options = {})
210
+ KayakoClient::TicketTimeTrack.get(ticket, id, options.merge(inherited_options))
211
+ end
212
+
213
+ alias_method :find_ticket_time_track, :get_ticket_time_track
214
+
215
+ def post_ticket_time_track(options = {})
216
+ KayakoClient::TicketTimeTrack.post(options.merge(inherited_options))
217
+ end
218
+
219
+ alias_method :create_ticket_time_track, :post_ticket_time_track
220
+
221
+ def delete_ticket_time_track(ticket, id, options = {})
222
+ KayakoClient::TicketTimeTrack.delete(ticket, id, options.merge(inherited_options))
223
+ end
224
+
225
+ alias_method :destroy_ticket_time_track, :delete_ticket_time_track
226
+
227
+
228
+ def ticket_types(options = {})
229
+ KayakoClient::TicketType.all(options.merge(inherited_options))
230
+ end
231
+
232
+ def get_ticket_type(id = :all, options = {})
233
+ KayakoClient::TicketType.get(id, options.merge(inherited_options))
234
+ end
235
+
236
+ alias_method :find_ticket_type, :get_ticket_type
237
+
238
+
239
+ def users(marker = nil, limit = nil, options = {})
240
+ KayakoClient::User.all(marker, limit, options.merge(inherited_options))
241
+ end
242
+
243
+ def get_user(id = :all, options = {})
244
+ KayakoClient::User.get(id, options.merge(inherited_options))
245
+ end
246
+
247
+ alias_method :find_user, :get_user
248
+
249
+ def post_user(options = {})
250
+ KayakoClient::User.post(options.merge(inherited_options))
251
+ end
252
+
253
+ alias_method :create_user, :post_user
254
+
255
+ def delete_user(id, options = {})
256
+ KayakoClient::User.delete(id, options.merge(inherited_options))
257
+ end
258
+
259
+ alias_method :destroy_user, :delete_user
260
+
261
+
262
+ def user_groups(options = {})
263
+ KayakoClient::UserGroup.all(options.merge(inherited_options))
264
+ end
265
+
266
+ def get_user_group(id = :all, options = {})
267
+ KayakoClient::UserGroup.get(id, options.merge(inherited_options))
268
+ end
269
+
270
+ alias_method :find_user_group, :get_user_group
271
+
272
+ def post_user_group(options = {})
273
+ KayakoClient::UserGroup.post(options.merge(inherited_options))
274
+ end
275
+
276
+ alias_method :create_user_group, :post_user_group
277
+
278
+ def delete_user_group(id, options = {})
279
+ KayakoClient::UserGroup.delete(id, options.merge(inherited_options))
280
+ end
281
+
282
+ alias_method :destroy_user_group, :delete_user_group
283
+
284
+
285
+ def user_organizations(options = {})
286
+ KayakoClient::UserOrganization.all(options.merge(inherited_options))
287
+ end
288
+
289
+ def get_user_organization(id = :all, options = {})
290
+ KayakoClient::UserOrganization.get(id, options.merge(inherited_options))
291
+ end
292
+
293
+ alias_method :find_user_organization, :get_user_organization
294
+
295
+ def post_user_organization(options = {})
296
+ KayakoClient::UserOrganization.post(options.merge(inherited_options))
297
+ end
298
+
299
+ alias_method :create_user_organization, :post_user_organization
300
+
301
+ def delete_user_organization(id, options = {})
302
+ KayakoClient::UserOrganization.delete(id, options.merge(inherited_options))
303
+ end
304
+
305
+ alias_method :destroy_user_organization, :delete_user_organization
306
+
307
+ end
308
+ end
@@ -0,0 +1,29 @@
1
+ module KayakoClient
2
+ module Logger
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ def logger=(log)
9
+ @logger = log
10
+ end
11
+
12
+ def logger
13
+ @logger ||= self.class.logger
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def logger=(log)
19
+ @@logger = log
20
+ end
21
+
22
+ def logger
23
+ @@logger ||= nil
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,456 @@
1
+ require 'base64'
2
+
3
+ module KayakoClient
4
+ module Object
5
+
6
+ PROPERTY_TYPES = [ :integer,
7
+ :string,
8
+ :symbol,
9
+ :boolean,
10
+ :date,
11
+ :object,
12
+ :binary
13
+ ].freeze
14
+
15
+ COMMON_OPTIONS = [ :in,
16
+ :required,
17
+ :condition,
18
+ :readonly,
19
+ :new
20
+ ].freeze
21
+
22
+ OPTIONS = { :integer => [ :range ],
23
+ :object => [ :class ]
24
+ }.freeze
25
+
26
+ def self.included(base)
27
+ base.extend(ClassMethods)
28
+ end
29
+
30
+ def to_i
31
+ instance_variable_defined?(:@id) ? instance_variable_get(:@id).to_i : 0
32
+ end
33
+
34
+ def []=(name, value)
35
+ raise ArgumentError, "object properties are read-only" if self.class.embedded?
36
+ raise ArgumentError, "property :#{name} does not exist" unless self.class.properties.include?(name.to_sym)
37
+ if !self.class.options[name.to_sym] || !self.class.options[name.to_sym][:readonly]
38
+ if self.class.options[name.to_sym] && self.class.options[name.to_sym][:new] && !new?
39
+ raise ArgumentError, "property :#{name} cannot be changed"
40
+ end
41
+ changes(name.to_sym)
42
+ @associated.delete(name.to_sym)
43
+ value = assign(self.class.properties[name.to_sym], value, self.class.options[name.to_sym] ? self.class.options[name.to_sym] : {})
44
+ instance_variable_set("@#{name}", value)
45
+ else
46
+ raise ArgumentError, "property :#{name} is read-only"
47
+ end
48
+ end
49
+
50
+ def [](name)
51
+ if self.class.properties.include?(name.to_sym)
52
+ send("#{name}")
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def errors
59
+ @errors ||= {}
60
+ end
61
+
62
+ def properties
63
+ self.class.properties.keys
64
+ end
65
+
66
+ def changed?
67
+ !changes.empty?
68
+ end
69
+
70
+ def new?
71
+ if defined?(@new)
72
+ @new
73
+ else
74
+ @new = true
75
+ end
76
+ end
77
+
78
+ def has_errors?
79
+ errors.size > 0
80
+ end
81
+
82
+ def loaded!
83
+ @changes = []
84
+ @new = false
85
+ end
86
+
87
+ module ClassMethods
88
+
89
+ def embedded
90
+ @embedded ||= true
91
+ end
92
+
93
+ def embedded?
94
+ @embedded ||= false
95
+ end
96
+
97
+ def supports(*args)
98
+ args.each do |method|
99
+ if %w{all get put post delete}.include?(method.to_s)
100
+ @supported_methods ||= []
101
+ @supported_methods << method.to_sym
102
+ else
103
+ logger.warn "ignored unsupported method :#{method}" if logger
104
+ end
105
+ end
106
+ end
107
+
108
+ def support?(method)
109
+ unless embedded?
110
+ defined?(@supported_methods) ? @supported_methods.include?(method) : true
111
+ else
112
+ false
113
+ end
114
+ end
115
+
116
+ def path(path = nil)
117
+ if path
118
+ @path = path
119
+ else
120
+ @path ||= '/' + self.superclass.name.split('::').last + '/' + self.name.split('::').last
121
+ end
122
+ end
123
+
124
+ def property(name, type, options = {})
125
+ if (type.is_a?(Array) && type.size == 1 && PROPERTY_TYPES.include?(type.first)) || PROPERTY_TYPES.include?(type)
126
+ init_variables
127
+ @properties[name] = type # save original type
128
+ type = type.first if type.is_a?(Array)
129
+ get_alias = options.delete(:get) if options[:get]
130
+ set_alias = options.delete(:set) if options[:set]
131
+ if (name.to_s.include?(?_))
132
+ altname = name.to_s.gsub(%r{_}, '')
133
+ get_alias = altname unless get_alias
134
+ set_alias = altname unless set_alias
135
+ end
136
+ # check options
137
+ options.each do |option, value|
138
+ if COMMON_OPTIONS.include?(option) || (OPTIONS.include?(type) && OPTIONS[type].include?(option))
139
+ @options[name] ||= {}
140
+ @options[name][option] = value
141
+ else
142
+ raise ArgumentError, "unsupported option: #{option}"
143
+ end
144
+ end
145
+ # check if :class was specified for :object
146
+ if type == :object
147
+ @options[name] ||= {}
148
+ @options[name][:readonly] = true
149
+ raise ArgumentError, ":object requires :class" unless @options[name][:class]
150
+ end
151
+ # define setter and getter methods
152
+ if !embedded? && (!@options[name] || !@options[name][:readonly])
153
+ define_method("#{name}=") do |value|
154
+ if self.class.options[name] && self.class.options[name][:new] && !new?
155
+ raise ArgumentError, "property :#{name} cannot be changed"
156
+ end
157
+ changes(name)
158
+ @associated.delete(name)
159
+ value = assign(self.class.properties[name], value, self.class.options[name] ? self.class.options[name] : {})
160
+ instance_variable_set("@#{name}", value)
161
+ end
162
+ if set_alias
163
+ alias_method("#{set_alias}=", "#{name}=")
164
+ @map[name] = set_alias.to_sym
165
+ end
166
+ end
167
+ define_method(name) do
168
+ instance_variable_defined?("@#{name}") ? instance_variable_get("@#{name}") : nil
169
+ end
170
+ alias_method("#{name}?", name) if type == :boolean
171
+ if get_alias
172
+ alias_method(get_alias, name)
173
+ @aliases[get_alias.to_sym] = name
174
+ end
175
+ else
176
+ raise ArgumentError, "unsupported type: #{type.inspect}"
177
+ end
178
+ end
179
+
180
+ def associate(name, property, object)
181
+ unless @properties.include?(property)
182
+ raise ArgumentError, "undefined property: #{property}; use 'property :#{property} ...' first"
183
+ end
184
+ klass = object.is_a?(Class) ? object : KayakoClient.const_get(object)
185
+ # method for access to associated object
186
+ define_method(name) do
187
+ if @associated.has_key?(property)
188
+ @associated[property]
189
+ elsif instance_variable_defined?("@#{property}")
190
+ id = instance_variable_get("@#{property}")
191
+ if id.is_a?(Array)
192
+ @associated[property] = id.inject([]) do |array, i|
193
+ array << klass.get(i.to_i, inherited_options)
194
+ array
195
+ end
196
+ elsif id.respond_to?('to_i') && id.to_i > 0
197
+ @associated[property] = klass.get(id.to_i, inherited_options)
198
+ else
199
+ @associated[property] = nil
200
+ end
201
+ else
202
+ @associated[property] = nil
203
+ end
204
+ end
205
+ end
206
+
207
+ def check_conditions(params)
208
+ errors = params.delete(:errors)
209
+ return unless errors
210
+ options.each do |property, option|
211
+ if params[property]
212
+ if option[:condition] && option[:condition].is_a?(Hash)
213
+ option[:condition].each do |name, value|
214
+ errors[property] = "condition not met" unless params[name] == value
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ def require_properties(method, params)
222
+ options.each do |property, option|
223
+ next if params[property]
224
+ if option[:required]
225
+ if (option[:required].is_a?(Symbol) && option[:required] == method) ||
226
+ (option[:required].is_a?(Array) && option[:required].include?(method))
227
+ raise ArgumentError, "missing :#{property}"
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ def validate(params)
234
+ errors = params.delete(:errors)
235
+ params.inject({}) do |hash, (property, value)|
236
+ if properties.include?(property)
237
+ begin
238
+ name = map[property] ? map[property] : property
239
+ hash[name] = convert(properties[property], value, options[property] ? options[property] : {})
240
+ rescue => error
241
+ errors[property] = error.message if errors
242
+ end
243
+ else
244
+ logger.warn "skipping validation of unknown property: #{property}" if logger
245
+ end
246
+ hash
247
+ end
248
+ end
249
+
250
+ def convert(type, value, options = {})
251
+ raise "property is readonly" if options[:readonly]
252
+ if type.is_a?(Array)
253
+ type = type.first
254
+ if value.is_a?(Hash) && value.size == 1
255
+ value = value.values.first
256
+ end
257
+ value = [ value ] unless value.is_a?(Array)
258
+ value.map! do |item|
259
+ convert_value(type, item, options)
260
+ end
261
+ else
262
+ value = convert_value(type, value, options)
263
+ end
264
+ value
265
+ end
266
+
267
+ def convert_value(type, value, options = {})
268
+ if options[:in] && options[:in].is_a?(Array)
269
+ raise "not in list of allowed values" unless options[:in].include?(value)
270
+ end
271
+ case type
272
+ when :integer
273
+ if options[:range] && options[:range].is_a?(Range)
274
+ raise "out of range" unless options[:range].include?(value)
275
+ end
276
+ result = value
277
+ when :symbol
278
+ result = value.to_s
279
+ when :boolean
280
+ result = (value == true) ? 1 : 0
281
+ when :date
282
+ result = value ? value.to_i : 0
283
+ when :object
284
+ raise RuntimeError, ":object cannot be used as a parameter"
285
+ when :binary
286
+ result = Base64.encode64(value).strip
287
+ else
288
+ result = value
289
+ end
290
+ result
291
+ end
292
+
293
+ def properties
294
+ @properties
295
+ end
296
+
297
+ def aliases
298
+ @aliases
299
+ end
300
+
301
+ def options
302
+ @options
303
+ end
304
+
305
+ def map
306
+ @map
307
+ end
308
+
309
+ private
310
+
311
+ def init_variables
312
+ @properties ||= {}
313
+ @aliases ||= {}
314
+ @options ||= {}
315
+ @map ||= {}
316
+ end
317
+
318
+ end
319
+
320
+ private
321
+
322
+ def clean
323
+ @associated = {}
324
+ self.class.properties.each do |property, type|
325
+ if instance_variable_defined?("@#{property}")
326
+ remove_instance_variable("@#{property}")
327
+ end
328
+ end
329
+ end
330
+
331
+ def import(options = {})
332
+ if options.size == 1 && self.class.properties.size > 1
333
+ values = options.values.first
334
+ options = values if values.is_a?(Hash)
335
+ end
336
+ return if options.empty?
337
+ options.each do |property, value|
338
+ name = self.class.aliases.include?(property) ? self.class.aliases[property] : property
339
+ if self.class.properties.include?(name)
340
+ unless self.class.embedded? || (self.class.options[name] && self.class.options[name][:readonly])
341
+ changes(name)
342
+ end
343
+ value = assign(self.class.properties[name], value, self.class.options[name] ? self.class.options[name] : {})
344
+ instance_variable_set("@#{name}", value)
345
+ else
346
+ logger.debug "unsupported property :#{property}" if logger
347
+ end
348
+ end
349
+ end
350
+
351
+ def validate(method, params)
352
+ end
353
+
354
+ def check_conditions(params)
355
+ @errors ||= {}
356
+ self.class.options.each do |property, options|
357
+ # check :condition
358
+ if params[property]
359
+ if options[:condition] && options[:condition].is_a?(Hash)
360
+ options[:condition].each do |name, value|
361
+ param = params[name] || (instance_variable_defined?("@#{name}") ? instance_variable_get("@#{name}") : nil)
362
+ @errors[property] = "condition not met" unless param == value
363
+ end
364
+ end
365
+ end
366
+ # check :new
367
+ if params.has_key?(property)
368
+ @errors[property] = "value cannot be changed" if options[:new] && !new?
369
+ end
370
+ end
371
+ end
372
+
373
+ def require_properties(method, params)
374
+ self.class.options.each do |property, options|
375
+ next if params[property]
376
+ if options[:required]
377
+ if (options[:required].is_a?(Symbol) && options[:required] == method) ||
378
+ (options[:required].is_a?(Array) && options[:required].include?(method))
379
+ params[property] = instance_variable_get("@#{property}") if instance_variable_defined?("@#{property}")
380
+ raise ArgumentError, "missing :#{property}" if params[property].nil?
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ def changes(property = nil)
387
+ @changes ||= []
388
+ unless property.nil?
389
+ @changes << property unless @changes.include?(property)
390
+ end
391
+ @changes
392
+ end
393
+
394
+ def assign(type, value, options = {})
395
+ if type.is_a?(Array)
396
+ type = type.first
397
+ if value.is_a?(Hash) && value.size == 1
398
+ value = value.values.first
399
+ end
400
+ value = [ value ] unless value.is_a?(Array)
401
+ value.map! do |item|
402
+ assign_value(type, item, options)
403
+ end
404
+ value.freeze if options[:readonly]
405
+ else
406
+ value = assign_value(type, value, options)
407
+ end
408
+ value
409
+ end
410
+
411
+ def assign_value(type, value, options = {})
412
+ case type
413
+ when :integer
414
+ value.to_i
415
+ when :string
416
+ value.to_s
417
+ when :symbol
418
+ value.to_sym
419
+ when :boolean
420
+ if value.respond_to?('to_i')
421
+ value.to_i == 0 ? false : true
422
+ else
423
+ case value
424
+ when TrueClass, FalseClass
425
+ value
426
+ else
427
+ !!value
428
+ end
429
+ end
430
+ when :date
431
+ case value
432
+ when Time
433
+ value
434
+ else
435
+ value.to_i > 0 ? Time.at(value.to_i) : nil
436
+ end
437
+ when :object
438
+ raise RuntimeError, "missing :class" unless options[:class]
439
+ klass = options[:class].is_a?(Class) ? options[:class] : KayakoClient.const_get(options[:class])
440
+ object = klass.new(value.merge(inherited_options))
441
+ object.loaded!
442
+ object
443
+ when :binary
444
+ if value =~ %r{^[A-Za-z0-9+/]+={0,3}$} && (value.size % 4) == 0
445
+ logger.debug "decoding base64 string" if logger
446
+ Base64.decode64(value)
447
+ else
448
+ value
449
+ end
450
+ else
451
+ value
452
+ end
453
+ end
454
+
455
+ end
456
+ end