cronofy 0.0.5 → 0.37.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,11 @@
1
1
  module Cronofy
2
2
  class CronofyError < StandardError
3
-
4
3
  end
5
4
 
6
5
  class CredentialsMissingError < CronofyError
7
-
8
6
  def initialize(message=nil)
9
7
  super(message || "No credentials supplied")
10
8
  end
11
-
12
9
  end
13
10
 
14
11
  class APIError < CronofyError
@@ -32,28 +29,91 @@ module Cronofy
32
29
  end
33
30
  end
34
31
 
35
- class NotFoundError < APIError
32
+ class BadRequestError < APIError
33
+ end
36
34
 
35
+ class NotFoundError < APIError
37
36
  end
38
37
 
39
38
  class AuthenticationFailureError < APIError
40
-
41
39
  end
42
40
 
43
41
  class AuthorizationFailureError < APIError
44
-
45
42
  end
46
43
 
47
44
  class InvalidRequestError < APIError
45
+ def message
46
+ "#{super} - #{errors.inspect}"
47
+ end
48
+
49
+ def errors
50
+ @errors ||= begin
51
+ json = JSON.parse(self.body)
52
+ json.fetch("errors", Hash.new)
53
+ rescue
54
+ Hash.new
55
+ end
56
+ end
57
+ end
48
58
 
59
+ class AccountLockedError < APIError
49
60
  end
50
61
 
51
62
  class TooManyRequestsError < APIError
63
+ end
52
64
 
65
+ class ServerError < APIError
53
66
  end
54
67
 
55
- class UnknownError < APIError
68
+ class PaymentRequiredError < APIError
69
+ end
56
70
 
71
+ class ServiceUnreachableError < APIError
57
72
  end
58
73
 
59
- end
74
+ class BadGatewayError < ServiceUnreachableError
75
+ end
76
+
77
+ class ServiceUnavailableError < ServiceUnreachableError
78
+ end
79
+
80
+ class GatewayTimeoutError < ServiceUnreachableError
81
+ end
82
+
83
+ class UnknownError < APIError
84
+ end
85
+
86
+ # Internal: Helper methods for raising more meaningful errors.
87
+ class Errors
88
+ ERROR_MAP = {
89
+ 400 => BadRequestError,
90
+ 401 => AuthenticationFailureError,
91
+ 402 => PaymentRequiredError,
92
+ 403 => AuthorizationFailureError,
93
+ 404 => NotFoundError,
94
+ 422 => InvalidRequestError,
95
+ 423 => AccountLockedError,
96
+ 429 => TooManyRequestsError,
97
+ 500 => ServerError,
98
+ 502 => BadGatewayError,
99
+ 503 => ServiceUnavailableError,
100
+ 504 => GatewayTimeoutError,
101
+ }.freeze
102
+
103
+ def self.map_error(error)
104
+ raise_error(error.response)
105
+ end
106
+
107
+ def self.raise_if_error(response)
108
+ return if response.status == 200
109
+ raise_error(response)
110
+ end
111
+
112
+ private
113
+
114
+ def self.raise_error(response)
115
+ error_class = ERROR_MAP.fetch(response.status, UnknownError)
116
+ raise error_class.new(response.headers['status'], response)
117
+ end
118
+ end
119
+ end
@@ -1,13 +1,46 @@
1
1
  require 'json'
2
2
 
3
3
  module Cronofy
4
+ # Internal: Class for dealing with the parsing of API responses.
4
5
  class ResponseParser
5
6
  def initialize(response)
6
7
  @response = response
7
8
  end
8
9
 
9
- def parse_json
10
- JSON.parse @response.body
10
+ def parse_collections(attribute_collection_types)
11
+ attribute_collection_types.each do |attribute, type|
12
+ return parse_collection(type, attribute.to_s) if json_hash[attribute.to_s]
13
+ end
14
+
15
+ raise "No mapped attributes for response - #{json_hash.keys}"
16
+ end
17
+
18
+ def parse_collection(type, attribute = nil)
19
+ target = parsing_target(attribute)
20
+ target.map { |item| type.new(item) }
21
+ end
22
+
23
+ def parse_json(type, attribute = nil)
24
+ target = parsing_target(attribute)
25
+ type.new(target)
26
+ end
27
+
28
+ def json
29
+ json_hash.dup
30
+ end
31
+
32
+ private
33
+
34
+ def json_hash
35
+ @json_hash ||= JSON.parse(@response.body)
36
+ end
37
+
38
+ def parsing_target(attribute)
39
+ if attribute
40
+ json_hash[attribute]
41
+ else
42
+ json_hash
43
+ end
11
44
  end
12
45
  end
13
- end
46
+ end
@@ -0,0 +1,31 @@
1
+ module Cronofy
2
+ module TimeEncoding
3
+ def encode_event_time(value)
4
+ case value
5
+ when String
6
+ value
7
+ when Hash
8
+ if value[:time]
9
+ encoded_time = encode_event_time(value[:time])
10
+ value.merge(time: encoded_time)
11
+ else
12
+ value
13
+ end
14
+ else
15
+ to_iso8601(value)
16
+ end
17
+ end
18
+
19
+ def to_iso8601(value)
20
+ case value
21
+ when NilClass, String
22
+ value
23
+ when Time
24
+ value.getutc.iso8601
25
+ else
26
+ value.iso8601
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,420 @@
1
+ require "date"
2
+ require "hashie"
3
+ require "time"
4
+
5
+ module Cronofy
6
+ class Credentials
7
+ class LinkingProfile
8
+ attr_reader :provider_name
9
+ attr_reader :profile_id
10
+ attr_reader :profile_name
11
+
12
+ def initialize(hash)
13
+ @provider_name = hash['provider_name'] || hash[:provider_name]
14
+ @profile_id = hash['profile_id'] || hash[:profile_id]
15
+ @profile_name = hash['profile_name'] || hash[:profile_name]
16
+ end
17
+
18
+ def to_h
19
+ {
20
+ provider_name: provider_name,
21
+ profile_id: profile_id,
22
+ profile_name: profile_name,
23
+ }
24
+ end
25
+
26
+ def ==(other)
27
+ case other
28
+ when LinkingProfile
29
+ self.provider_name == other.provider_name &&
30
+ self.profile_id == other.profile_id &&
31
+ self.profile_name == other.profile_name
32
+ end
33
+ end
34
+ end
35
+
36
+ attr_reader :access_token
37
+ attr_reader :account_id
38
+ attr_reader :application_calendar_id
39
+ attr_reader :sub
40
+ attr_reader :expires_at
41
+ attr_reader :expires_in
42
+ attr_reader :linking_profile
43
+ attr_reader :refresh_token
44
+ attr_reader :scope
45
+
46
+ def initialize(oauth_token)
47
+ @access_token = oauth_token.token
48
+ @account_id = oauth_token.params['account_id']
49
+ @application_calendar_id = oauth_token.params['application_calendar_id']
50
+ @sub = oauth_token.params['sub']
51
+ @expires_at = oauth_token.expires_at
52
+ @expires_in = oauth_token.expires_in
53
+ @refresh_token = oauth_token.refresh_token
54
+ @scope = oauth_token.params['scope']
55
+
56
+ if details = oauth_token.params['linking_profile']
57
+ @linking_profile = LinkingProfile.new(details)
58
+ end
59
+ end
60
+
61
+ def to_h
62
+ hash = {
63
+ access_token: access_token,
64
+ expires_at: expires_at,
65
+ expires_in: expires_in,
66
+ refresh_token: refresh_token,
67
+ scope: scope,
68
+ }
69
+
70
+ if account_id
71
+ hash[:account_id] = account_id
72
+ end
73
+
74
+ if application_calendar_id
75
+ hash[:application_calendar_id] = application_calendar_id
76
+ end
77
+
78
+ if sub
79
+ hash[:sub] = sub
80
+ end
81
+
82
+ if linking_profile
83
+ hash[:linking_profile] = linking_profile.to_h
84
+ end
85
+
86
+ hash
87
+ end
88
+
89
+ def to_hash
90
+ warn "#to_hash has been deprecated, use #to_h instead"
91
+ to_h
92
+ end
93
+ end
94
+
95
+ module ISO8601Time
96
+ def self.coerce(value)
97
+ case value
98
+ when Time
99
+ value
100
+ when String
101
+ Time.iso8601(value)
102
+ else
103
+ raise ArgumentError, "Cannot coerce #{value.inspect} to Time"
104
+ end
105
+ end
106
+ end
107
+
108
+ class DateOrTime
109
+ def initialize(args)
110
+ # Prefer time if both provided as it is more accurate
111
+ if args[:time]
112
+ @time = args[:time]
113
+ else
114
+ @date = args[:date]
115
+ end
116
+ end
117
+
118
+ def self.coerce(value)
119
+ begin
120
+ time = ISO8601Time.coerce(value)
121
+ rescue
122
+ begin
123
+ date = Date.strptime(value, '%Y-%m-%d')
124
+ rescue
125
+ end
126
+ end
127
+
128
+ coerced = self.new(time: time, date: date)
129
+
130
+ raise "Failed to coerce \"#{value}\"" unless coerced.time? or coerced.date?
131
+
132
+ coerced
133
+ end
134
+
135
+ def date
136
+ @date
137
+ end
138
+
139
+ def date?
140
+ !!@date
141
+ end
142
+
143
+ def time
144
+ @time
145
+ end
146
+
147
+ def time?
148
+ !!@time
149
+ end
150
+
151
+ def to_date
152
+ if date?
153
+ date
154
+ else
155
+ time.to_date
156
+ end
157
+ end
158
+
159
+ def to_time
160
+ if time?
161
+ time
162
+ else
163
+ # Convert dates to UTC time, not local time
164
+ Time.utc(date.year, date.month, date.day)
165
+ end
166
+ end
167
+
168
+ def ==(other)
169
+ case other
170
+ when DateOrTime
171
+ if self.time?
172
+ other.time? and self.time == other.time
173
+ elsif self.date?
174
+ other.date? and self.date == other.date
175
+ else
176
+ # Both neither date nor time
177
+ self.time? == other.time? and self.date? == other.date?
178
+ end
179
+ else
180
+ false
181
+ end
182
+ end
183
+
184
+ def inspect
185
+ to_s
186
+ end
187
+
188
+ def to_s
189
+ if time?
190
+ "<#{self.class} time=#{self.time}>"
191
+ elsif date?
192
+ "<#{self.class} date=#{self.date}>"
193
+ else
194
+ "<#{self.class} empty>"
195
+ end
196
+ end
197
+ end
198
+
199
+ class CronofyMash < Hashie::Mash
200
+ include Hashie::Extensions::Coercion
201
+
202
+ disable_warnings if respond_to?(:disable_warnings)
203
+ end
204
+
205
+ class Account < CronofyMash
206
+ end
207
+
208
+ BatchEntry = Struct.new(:request, :response)
209
+
210
+ class BatchEntryRequest < CronofyMash
211
+ end
212
+
213
+ class BatchEntryResponse < CronofyMash
214
+ end
215
+
216
+ class BatchResponse
217
+ class PartialSuccessError < CronofyError
218
+ attr_reader :batch_response
219
+
220
+ def initialize(message, batch_response)
221
+ super(message)
222
+ @batch_response = batch_response
223
+ end
224
+ end
225
+
226
+ attr_reader :entries
227
+
228
+ def initialize(entries)
229
+ @entries = entries
230
+ end
231
+
232
+ def errors
233
+ entries.select { |entry| (entry.status % 100) != 2 }
234
+ end
235
+
236
+ def errors?
237
+ errors.any?
238
+ end
239
+ end
240
+
241
+ class UserInfo < CronofyMash
242
+ end
243
+
244
+ class Calendar < CronofyMash
245
+ end
246
+
247
+ class Channel < CronofyMash
248
+ end
249
+
250
+ class Resource < CronofyMash
251
+ end
252
+
253
+ class EventTime
254
+ attr_reader :time
255
+ attr_reader :tzid
256
+
257
+ def initialize(time, tzid)
258
+ @time = time
259
+ @tzid = tzid
260
+ end
261
+
262
+ def self.coerce(value)
263
+ case value
264
+ when String
265
+ DateOrTime.coerce(value)
266
+ when Hash
267
+ time_value = value["time"]
268
+ tzid = value["tzid"]
269
+
270
+ date_or_time = DateOrTime.coerce(time_value)
271
+
272
+ new(date_or_time, tzid)
273
+ end
274
+ end
275
+
276
+ def ==(other)
277
+ case other
278
+ when EventTime
279
+ self.time == other.time && self.tzid == other.tzid
280
+ else
281
+ false
282
+ end
283
+ end
284
+ end
285
+
286
+ class Event < CronofyMash
287
+ coerce_key :start, EventTime
288
+ coerce_key :end, EventTime
289
+
290
+ coerce_key :created, ISO8601Time
291
+ coerce_key :updated, ISO8601Time
292
+ end
293
+
294
+ module Events
295
+ def self.coerce(values)
296
+ values.map { |v| Event.new(v) }
297
+ end
298
+ end
299
+
300
+ class PagedEventsResult < CronofyMash
301
+ coerce_key :events, Events
302
+ end
303
+
304
+ class FreeBusy < CronofyMash
305
+ coerce_key :start, EventTime
306
+ coerce_key :end, EventTime
307
+ end
308
+
309
+ module FreeBusyEnumerable
310
+ def self.coerce(values)
311
+ values.map { |v| FreeBusy.new(v) }
312
+ end
313
+ end
314
+
315
+ class PagedFreeBusyResult < CronofyMash
316
+ coerce_key :free_busy, FreeBusyEnumerable
317
+ end
318
+
319
+ class Profile < CronofyMash
320
+ end
321
+
322
+ class PermissionsResponse < CronofyMash
323
+ end
324
+
325
+ class Participant < CronofyMash
326
+ end
327
+
328
+ class AddToCalendarResponse < CronofyMash
329
+ end
330
+
331
+ class Proposal < CronofyMash
332
+ coerce_key :start, EventTime
333
+ coerce_key :end, EventTime
334
+ end
335
+
336
+ class SmartInviteReply < CronofyMash
337
+ coerce_key :proposal, Proposal
338
+ end
339
+
340
+ module SmartInviteReplyEnumerable
341
+ def self.coerce(values)
342
+ values.map { |v| SmartInviteReply.new(v) }
343
+ end
344
+ end
345
+
346
+ class SmartInviteResponse < CronofyMash
347
+ coerce_key :recipient, SmartInviteReply
348
+ coerce_key :replies, SmartInviteReplyEnumerable
349
+ end
350
+
351
+ module ParticipantEnumerable
352
+ def self.coerce(values)
353
+ values.map { |v| Participant.new(v) }
354
+ end
355
+ end
356
+
357
+ class SequenceItem < CronofyMash
358
+ coerce_key :start, EventTime
359
+ coerce_key :end, EventTime
360
+
361
+ coerce_key :participants, ParticipantEnumerable
362
+ end
363
+
364
+ module SequenceItemEnumerable
365
+ def self.coerce(values)
366
+ values.map { |v| SequenceItem.new(v) }
367
+ end
368
+ end
369
+
370
+ class Sequence < CronofyMash
371
+ coerce_key :sequence, SequenceItemEnumerable
372
+ end
373
+
374
+ class AvailablePeriod < CronofyMash
375
+ coerce_key :start, EventTime
376
+ coerce_key :end, EventTime
377
+
378
+ coerce_key :participants, ParticipantEnumerable
379
+ end
380
+
381
+ class AvailableSlot < CronofyMash
382
+ coerce_key :start, EventTime
383
+ coerce_key :end, EventTime
384
+
385
+ coerce_key :participants, ParticipantEnumerable
386
+ end
387
+
388
+ class ElementToken < CronofyMash
389
+ end
390
+
391
+ class SchedulingConversation < CronofyMash
392
+ end
393
+
394
+ class SchedulingConversationResponse < CronofyMash
395
+ coerce_key :participant, Participant
396
+ coerce_key :scheduling_conversation, SchedulingConversation
397
+ end
398
+
399
+ class SchedulingConversationSlot < CronofyMash
400
+ coerce_key :start, EventTime
401
+ coerce_key :end, EventTime
402
+ end
403
+
404
+ class WeeklyPeriod < CronofyMash
405
+ end
406
+
407
+ module WeeklyPeriodEnumerable
408
+ def self.coerce(values)
409
+ values.map { |v| WeeklyPeriod.new(v) }
410
+ end
411
+ end
412
+
413
+ class AvailabilityRule < CronofyMash
414
+ coerce_key :weekly_periods, WeeklyPeriodEnumerable
415
+ end
416
+
417
+ class RealTimeSchedulingStatus < CronofyMash
418
+ coerce_key :event, Event
419
+ end
420
+ end
@@ -1,3 +1,3 @@
1
1
  module Cronofy
2
- VERSION = "0.0.5"
2
+ VERSION = "0.37.7".freeze
3
3
  end
data/lib/cronofy.rb CHANGED
@@ -1,26 +1,90 @@
1
1
  require "cronofy/version"
2
2
  require "cronofy/errors"
3
+ require "cronofy/types"
4
+ require "cronofy/api_key"
3
5
  require "cronofy/auth"
6
+ require "cronofy/time_encoding"
4
7
  require "cronofy/client"
5
8
  require "cronofy/response_parser"
9
+
10
+ require 'base64'
6
11
  require 'json'
12
+ require 'openssl'
7
13
 
8
14
  module Cronofy
15
+ def self.default_data_centre
16
+ default_data_center
17
+ end
18
+
19
+ def self.default_data_centre=(value)
20
+ default_data_center= value
21
+ end
22
+
23
+ def self.default_data_center
24
+ @default_data_center || ENV['CRONOFY_DATA_CENTER'] || ENV['CRONOFY_DATA_CENTRE']
25
+ end
26
+
27
+ def self.default_data_center=(value)
28
+ @default_data_center = value
29
+ end
9
30
 
10
- def self.api_url
11
- @api_url ||= (ENV['CRONOFY_API_URL'] || "https://api.cronofy.com")
31
+ def self.api_url(data_center_override)
32
+ if data_center_override
33
+ api_url_for_data_center(data_center_override)
34
+ else
35
+ ENV['CRONOFY_API_URL'] || api_url_for_data_center(default_data_center)
36
+ end
12
37
  end
13
38
 
14
39
  def self.api_url=(value)
15
40
  @api_url = value
16
41
  end
17
42
 
18
- def self.app_url
19
- @app_url ||= (ENV['CRONOFY_APP_URL'] || "https://app.cronofy.com")
43
+ def self.api_url_for_data_centre
44
+ api_url_for_data_center
45
+ end
46
+
47
+ def self.api_url_for_data_center(dc)
48
+ @api_urls ||= Hash.new do |hash, key|
49
+ if key.nil? || key.to_sym == :us
50
+ url = "https://api.cronofy.com"
51
+ else
52
+ url = "https://api-#{key}.cronofy.com"
53
+ end
54
+
55
+ hash[key] = url.freeze
56
+ end
57
+
58
+ @api_urls[dc]
59
+ end
60
+
61
+ def self.app_url(data_center_override)
62
+ if data_center_override
63
+ app_url_for_data_center(data_center_override)
64
+ else
65
+ ENV['CRONOFY_APP_URL'] || app_url_for_data_center(default_data_center)
66
+ end
20
67
  end
21
68
 
22
69
  def self.app_url=(value)
23
70
  @app_url = value
24
71
  end
25
72
 
73
+ def self.app_url_for_data_centre(dc)
74
+ app_url_for_data_center(dc)
75
+ end
76
+
77
+ def self.app_url_for_data_center(dc)
78
+ @app_urls ||= Hash.new do |hash, key|
79
+ if key.nil? || key.to_sym == :us
80
+ url = "https://app.cronofy.com"
81
+ else
82
+ url = "https://app-#{key}.cronofy.com"
83
+ end
84
+
85
+ hash[key] = url.freeze
86
+ end
87
+
88
+ @app_urls[dc]
89
+ end
26
90
  end