cronofy 0.0.5 → 0.37.7

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.
@@ -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