supercast 0.0.2
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.
- checksums.yaml +7 -0
- data/bin/supercast-console.rb +24 -0
- data/lib/supercast/client.rb +552 -0
- data/lib/supercast/data_list.rb +110 -0
- data/lib/supercast/data_object.rb +410 -0
- data/lib/supercast/data_types.rb +21 -0
- data/lib/supercast/errors.rb +73 -0
- data/lib/supercast/operations/create.rb +19 -0
- data/lib/supercast/operations/destroy.rb +42 -0
- data/lib/supercast/operations/list.rb +36 -0
- data/lib/supercast/operations/request.rb +65 -0
- data/lib/supercast/operations/save.rb +87 -0
- data/lib/supercast/resource.rb +79 -0
- data/lib/supercast/resources/channel.rb +9 -0
- data/lib/supercast/resources/creator.rb +11 -0
- data/lib/supercast/resources/episode.rb +12 -0
- data/lib/supercast/resources/invite.rb +10 -0
- data/lib/supercast/resources/role.rb +12 -0
- data/lib/supercast/resources/subscriber.rb +12 -0
- data/lib/supercast/resources/usage_alert.rb +28 -0
- data/lib/supercast/resources.rb +9 -0
- data/lib/supercast/response.rb +48 -0
- data/lib/supercast/singleton.rb +25 -0
- data/lib/supercast/util.rb +318 -0
- data/lib/supercast/version.rb +5 -0
- data/lib/supercast.rb +145 -0
- data/spec/lib/supercast/resource_spec.rb +11 -0
- data/spec/lib/supercast/version_spec.rb +9 -0
- data/spec/lib/supercast_spec.rb +39 -0
- data/spec/spec_helper.rb +111 -0
- metadata +147 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
class DataObject
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
@@permanent_attributes = Set.new([:id]) # rubocop:disable Style/ClassVars
|
|
8
|
+
|
|
9
|
+
# The default :id method is deprecated and isn't useful to us
|
|
10
|
+
undef :id if method_defined?(:id)
|
|
11
|
+
|
|
12
|
+
def initialize(id = nil, opts = {})
|
|
13
|
+
id, @retrieve_params = Util.normalize_id(id)
|
|
14
|
+
@opts = Util.normalize_opts(opts)
|
|
15
|
+
@original_values = {}
|
|
16
|
+
@values = {}
|
|
17
|
+
# This really belongs in Resource, but not putting it there allows us
|
|
18
|
+
# to have a unified inspect method
|
|
19
|
+
@unsaved_values = Set.new
|
|
20
|
+
@transient_values = Set.new
|
|
21
|
+
@values[:id] = id if id
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.construct_from(values, opts = {})
|
|
25
|
+
values = Supercast::Util.symbolize_names(values)
|
|
26
|
+
|
|
27
|
+
# work around protected #initialize_from for now
|
|
28
|
+
new(values[:id]).send(:initialize_from, values, opts)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Determines the equality of two Supercast objects. Supercast objects are
|
|
32
|
+
# considered to be equal if they have the same set of values and each one
|
|
33
|
+
# of those values is the same.
|
|
34
|
+
def ==(other)
|
|
35
|
+
other.is_a?(DataObject) &&
|
|
36
|
+
@values == other.instance_variable_get(:@values)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Hash equality. As with `#==`, we consider two equivalent Supercast objects
|
|
40
|
+
# equal.
|
|
41
|
+
def eql?(other)
|
|
42
|
+
# Defer to the implementation on `#==`.
|
|
43
|
+
self == other
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# As with equality in `#==` and `#eql?`, we hash two Supercast objects to the
|
|
47
|
+
# same value if they're equivalent objects.
|
|
48
|
+
def hash
|
|
49
|
+
@values.hash
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_s(*_args)
|
|
53
|
+
JSON.pretty_generate(to_hash)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def inspect
|
|
57
|
+
id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ''
|
|
58
|
+
"#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " +
|
|
59
|
+
JSON.pretty_generate(@values)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Mass assigns attributes on the model.
|
|
63
|
+
#
|
|
64
|
+
# This is a version of +update_attributes+ that takes some extra options
|
|
65
|
+
# for internal use.
|
|
66
|
+
#
|
|
67
|
+
# ==== Attributes
|
|
68
|
+
#
|
|
69
|
+
# * +values+ - Hash of values to use to update the current attributes of
|
|
70
|
+
# the object.
|
|
71
|
+
# * +opts+ - Options for +DataObject+ like an API key that will be reused
|
|
72
|
+
# on subsequent API calls.
|
|
73
|
+
#
|
|
74
|
+
# ==== Options
|
|
75
|
+
#
|
|
76
|
+
# * +:dirty+ - Whether values should be initiated as "dirty" (unsaved) and
|
|
77
|
+
# which applies only to new DataObjects being initiated under this
|
|
78
|
+
# DataObject. Defaults to true.
|
|
79
|
+
def update_attributes(values, opts = {}, dirty: true)
|
|
80
|
+
values.each do |k, v|
|
|
81
|
+
add_accessors([k], values) unless metaclass.method_defined?(k.to_sym)
|
|
82
|
+
@values[k] = Util.convert_to_supercast_object(v, opts)
|
|
83
|
+
dirty_value!(@values[k]) if dirty
|
|
84
|
+
@unsaved_values.add(k)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def [](key)
|
|
89
|
+
@values[key.to_sym]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def []=(key, value)
|
|
93
|
+
send(:"#{key}=", value)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def keys
|
|
97
|
+
@values.keys
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def values
|
|
101
|
+
@values.values
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_json(_opts)
|
|
105
|
+
JSON.generate(@values)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def as_json(*opts)
|
|
109
|
+
@values.as_json(*opts)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_hash
|
|
113
|
+
maybe_to_hash = lambda do |value|
|
|
114
|
+
value&.respond_to?(:to_hash) ? value.to_hash : value
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
@values.each_with_object({}) do |(key, value), acc|
|
|
118
|
+
acc[key] = case value
|
|
119
|
+
when Array
|
|
120
|
+
value.map(&maybe_to_hash)
|
|
121
|
+
else
|
|
122
|
+
maybe_to_hash.call(value)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def each(&blk)
|
|
128
|
+
@values.each(&blk)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Sets all keys within the DataObject as unsaved so that they will be
|
|
132
|
+
# included with an update when #serialize_params is called.
|
|
133
|
+
def dirty!
|
|
134
|
+
@unsaved_values = Set.new(@values.keys)
|
|
135
|
+
|
|
136
|
+
@values.each_value do |v|
|
|
137
|
+
dirty_value!(v)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Implements custom encoding for Ruby's Marshal. The data produced by this
|
|
142
|
+
# method should be comprehendable by #marshal_load.
|
|
143
|
+
#
|
|
144
|
+
# This allows us to remove certain features that cannot or should not be
|
|
145
|
+
# serialized.
|
|
146
|
+
def marshal_dump
|
|
147
|
+
# The Client instance in @opts is not serializable and is not
|
|
148
|
+
# really a property of the DataObject, so we exclude it when
|
|
149
|
+
# dumping
|
|
150
|
+
opts = @opts.clone
|
|
151
|
+
opts.delete(:client)
|
|
152
|
+
[@values, opts]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Implements custom decoding for Ruby's Marshal. Consumes data that's
|
|
156
|
+
# produced by #marshal_dump.
|
|
157
|
+
def marshal_load(data)
|
|
158
|
+
values, opts = data
|
|
159
|
+
initialize(values[:id])
|
|
160
|
+
initialize_from(values, opts)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def serialize_params(options = {})
|
|
164
|
+
update_hash = {}
|
|
165
|
+
|
|
166
|
+
@values.each do |k, _v|
|
|
167
|
+
# There are a few reasons that we may want to add in a parameter for
|
|
168
|
+
# update:
|
|
169
|
+
#
|
|
170
|
+
# 1. The `force` option has been set.
|
|
171
|
+
# 2. We know that it was modified.
|
|
172
|
+
#
|
|
173
|
+
unsaved = @unsaved_values.include?(k)
|
|
174
|
+
next unless options[:force] || unsaved
|
|
175
|
+
|
|
176
|
+
update_hash[k.to_sym] = serialize_params_value(
|
|
177
|
+
@values[k], @original_values[k], unsaved, options[:force], key: k
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# a `nil` that makes it out of `#serialize_params_value` signals an empty
|
|
182
|
+
# value that we shouldn't appear in the serialized form of the object
|
|
183
|
+
update_hash.reject! { |_, v| v.nil? }
|
|
184
|
+
|
|
185
|
+
update_hash
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# A protected field is one that doesn't get an accessor assigned to it
|
|
189
|
+
# (i.e. `obj.public = ...`) and one which is not allowed to be updated via
|
|
190
|
+
# the class level `Model.update(id, { ... })`.
|
|
191
|
+
def self.protected_fields
|
|
192
|
+
[]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
protected
|
|
196
|
+
|
|
197
|
+
def metaclass
|
|
198
|
+
class << self; self; end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def remove_accessors(keys)
|
|
202
|
+
# not available in the #instance_eval below
|
|
203
|
+
protected_fields = self.class.protected_fields
|
|
204
|
+
|
|
205
|
+
metaclass.instance_eval do
|
|
206
|
+
keys.each do |k|
|
|
207
|
+
next if protected_fields.include?(k)
|
|
208
|
+
next if @@permanent_attributes.include?(k)
|
|
209
|
+
|
|
210
|
+
# Remove methods for the accessor's reader and writer.
|
|
211
|
+
[k, :"#{k}=", :"#{k}?"].each do |method_name|
|
|
212
|
+
next unless method_defined?(method_name)
|
|
213
|
+
|
|
214
|
+
begin
|
|
215
|
+
remove_method(method_name)
|
|
216
|
+
rescue NameError
|
|
217
|
+
warn("WARNING: Unable to remove method `#{method_name}`; " \
|
|
218
|
+
"if custom, please consider renaming to a name that doesn't " \
|
|
219
|
+
'collide with an API property name.')
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def add_accessors(keys, values)
|
|
227
|
+
# not available in the #instance_eval below
|
|
228
|
+
protected_fields = self.class.protected_fields
|
|
229
|
+
|
|
230
|
+
metaclass.instance_eval do
|
|
231
|
+
keys.each do |k|
|
|
232
|
+
next if protected_fields.include?(k)
|
|
233
|
+
next if @@permanent_attributes.include?(k)
|
|
234
|
+
|
|
235
|
+
if k == :method
|
|
236
|
+
# Object#method is a built-in Ruby method that accepts a symbol
|
|
237
|
+
# and returns the corresponding Method object. Because the API may
|
|
238
|
+
# also use `method` as a field name, we check the arity of *args
|
|
239
|
+
# to decide whether to act as a getter or call the parent method.
|
|
240
|
+
define_method(k) { |*args| args.empty? ? @values[k] : super(*args) }
|
|
241
|
+
else
|
|
242
|
+
define_method(k) { @values[k] }
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
define_method(:"#{k}=") do |v|
|
|
246
|
+
if v == ''
|
|
247
|
+
raise ArgumentError, "You cannot set #{k} to an empty string. " \
|
|
248
|
+
'We interpret empty strings as nil in requests. ' \
|
|
249
|
+
"You may set (object).#{k} = nil to delete the property."
|
|
250
|
+
end
|
|
251
|
+
@values[k] = Util.convert_to_supercast_object(v, @opts)
|
|
252
|
+
dirty_value!(@values[k])
|
|
253
|
+
@unsaved_values.add(k)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
define_method(:"#{k}?") { @values[k] } if [FalseClass, TrueClass].include?(values[k].class)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Disabling the cop because it's confused by the fact that the methods are
|
|
262
|
+
# protected, but we do define `#respond_to_missing?` just below. Hopefully
|
|
263
|
+
# this is fixed in more recent Rubocop versions.
|
|
264
|
+
def method_missing(name, *args)
|
|
265
|
+
# TODO: only allow setting in updateable classes.
|
|
266
|
+
if name.to_s.end_with?('=')
|
|
267
|
+
attr = name.to_s[0...-1].to_sym
|
|
268
|
+
|
|
269
|
+
# Pull out the assigned value. This is only used in the case of a
|
|
270
|
+
# boolean value to add a question mark accessor (i.e. `foo?`) for
|
|
271
|
+
# convenience.
|
|
272
|
+
val = args.first
|
|
273
|
+
|
|
274
|
+
# the second argument is only required when adding boolean accessors
|
|
275
|
+
add_accessors([attr], attr => val)
|
|
276
|
+
|
|
277
|
+
begin
|
|
278
|
+
mth = method(name)
|
|
279
|
+
rescue NameError
|
|
280
|
+
raise NoMethodError,
|
|
281
|
+
"Cannot set #{attr} on this object. HINT: you can't set: " \
|
|
282
|
+
"#{@@permanent_attributes.to_a.join(', ')}"
|
|
283
|
+
end
|
|
284
|
+
return mth.call(args[0])
|
|
285
|
+
elsif @values.key?(name)
|
|
286
|
+
return @values[name]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
begin
|
|
290
|
+
super
|
|
291
|
+
rescue NoMethodError => e
|
|
292
|
+
# If we notice the accessed name if our set of transient values we can
|
|
293
|
+
# give the user a slightly more helpful error message. If not, just
|
|
294
|
+
# raise right away.
|
|
295
|
+
raise unless @transient_values.include?(name)
|
|
296
|
+
|
|
297
|
+
raise NoMethodError,
|
|
298
|
+
e.message + ". HINT: The '#{name}' attribute was set in the " \
|
|
299
|
+
'past, however. It was then wiped when refreshing the object ' \
|
|
300
|
+
"with the result returned by Supercast's API, probably as a " \
|
|
301
|
+
'result of a save(). The attributes currently available on ' \
|
|
302
|
+
"this object are: #{@values.keys.join(', ')}"
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def respond_to_missing?(symbol, include_private = false)
|
|
307
|
+
@values&.key?(symbol) || super
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Re-initializes the object based on a hash of values (usually one that's
|
|
311
|
+
# come back from an API call). Adds or removes value accessors as necessary
|
|
312
|
+
# and updates the state of internal data.
|
|
313
|
+
#
|
|
314
|
+
# Protected on purpose! Please do not expose.
|
|
315
|
+
#
|
|
316
|
+
# ==== Options
|
|
317
|
+
#
|
|
318
|
+
# * +:values:+ Hash used to update accessors and values.
|
|
319
|
+
# * +:opts:+ Options for DataObject like an API key.
|
|
320
|
+
def initialize_from(values, opts)
|
|
321
|
+
@opts = Util.normalize_opts(opts)
|
|
322
|
+
|
|
323
|
+
# the `#send` is here so that we can keep this method private
|
|
324
|
+
@original_values = self.class.send(:deep_copy, values)
|
|
325
|
+
|
|
326
|
+
removed = Set.new(@values.keys - values.keys)
|
|
327
|
+
added = Set.new(values.keys - @values.keys)
|
|
328
|
+
|
|
329
|
+
remove_accessors(removed)
|
|
330
|
+
add_accessors(added, values)
|
|
331
|
+
|
|
332
|
+
removed.each do |k|
|
|
333
|
+
@values.delete(k)
|
|
334
|
+
@transient_values.add(k)
|
|
335
|
+
@unsaved_values.delete(k)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
update_attributes(values, opts, dirty: false)
|
|
339
|
+
|
|
340
|
+
values.each_key do |k|
|
|
341
|
+
@transient_values.delete(k)
|
|
342
|
+
@unsaved_values.delete(k)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
self
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def serialize_params_value(value, original, _unsaved, force)
|
|
349
|
+
if value.nil?
|
|
350
|
+
''
|
|
351
|
+
elsif value.is_a?(Array)
|
|
352
|
+
update = value.map { |v| serialize_params_value(v, nil, true, force) }
|
|
353
|
+
|
|
354
|
+
# This prevents an array that's unchanged from being resent.
|
|
355
|
+
update if update != serialize_params_value(original, nil, true, force)
|
|
356
|
+
|
|
357
|
+
# Handle a Hash for now, but in the long run we should be able to
|
|
358
|
+
# eliminate all places where hashes are stored as values internally by
|
|
359
|
+
# making sure any time one is set, we convert it to a DataObject. This
|
|
360
|
+
# will simplify our model by making data within an object more
|
|
361
|
+
# consistent.
|
|
362
|
+
#
|
|
363
|
+
# For now, you can still run into a hash if someone appends one to an
|
|
364
|
+
# existing array being held by a DataObject. This could happen for
|
|
365
|
+
# example by appending a new hash onto `additional_owners` for an
|
|
366
|
+
# account.
|
|
367
|
+
elsif value.is_a?(Hash)
|
|
368
|
+
Util.convert_to_supercast_object(value, @opts).serialize_params
|
|
369
|
+
elsif value.is_a?(DataObject)
|
|
370
|
+
value.serialize_params(force: force)
|
|
371
|
+
else
|
|
372
|
+
value
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Produces a deep copy of the given object including support for arrays,
|
|
377
|
+
# hashes, and DataObjects.
|
|
378
|
+
private_class_method def self.deep_copy(obj)
|
|
379
|
+
case obj
|
|
380
|
+
when Array
|
|
381
|
+
obj.map { |e| deep_copy(e) }
|
|
382
|
+
when Hash
|
|
383
|
+
obj.each_with_object({}) do |(k, v), copy|
|
|
384
|
+
copy[k] = deep_copy(v)
|
|
385
|
+
copy
|
|
386
|
+
end
|
|
387
|
+
when DataObject
|
|
388
|
+
obj.class.construct_from(
|
|
389
|
+
deep_copy(obj.instance_variable_get(:@values)),
|
|
390
|
+
obj.instance_variable_get(:@opts).select do |k, _v|
|
|
391
|
+
Util::OPTS_COPYABLE.include?(k)
|
|
392
|
+
end
|
|
393
|
+
)
|
|
394
|
+
else
|
|
395
|
+
obj
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
private
|
|
400
|
+
|
|
401
|
+
def dirty_value!(value)
|
|
402
|
+
case value
|
|
403
|
+
when Array
|
|
404
|
+
value.map { |v| dirty_value!(v) }
|
|
405
|
+
when DataObject
|
|
406
|
+
value.dirty!
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
module DataTypes
|
|
5
|
+
def self.object_names_to_classes
|
|
6
|
+
{
|
|
7
|
+
# data structures
|
|
8
|
+
DataList::OBJECT_NAME => DataList,
|
|
9
|
+
|
|
10
|
+
# business objects
|
|
11
|
+
Channel::OBJECT_NAME => Channel,
|
|
12
|
+
Creator::OBJECT_NAME => Creator,
|
|
13
|
+
Episode::OBJECT_NAME => Episode,
|
|
14
|
+
Invite::OBJECT_NAME => Invite,
|
|
15
|
+
Role::OBJECT_NAME => Role,
|
|
16
|
+
Subscriber::OBJECT_NAME => Subscriber,
|
|
17
|
+
UsageAlert::OBJECT_NAME => UsageAlert
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
# SupercastError is the base error from which all other more specific Supercast
|
|
5
|
+
# errors derive.
|
|
6
|
+
class SupercastError < StandardError
|
|
7
|
+
attr_reader :message
|
|
8
|
+
|
|
9
|
+
# Response contains a SupercastResponse object that has some basic information
|
|
10
|
+
# about the response that conveyed the error.
|
|
11
|
+
attr_accessor :response
|
|
12
|
+
|
|
13
|
+
attr_reader :code
|
|
14
|
+
attr_reader :http_body
|
|
15
|
+
attr_reader :http_headers
|
|
16
|
+
attr_reader :http_status
|
|
17
|
+
attr_reader :json_body # equivalent to #data
|
|
18
|
+
|
|
19
|
+
# Initializes a SupercastError.
|
|
20
|
+
def initialize(message = nil, http_status: nil, http_body: nil, json_body: nil, http_headers: nil, code: nil)
|
|
21
|
+
@message = message
|
|
22
|
+
@http_status = http_status
|
|
23
|
+
@http_body = http_body
|
|
24
|
+
@http_headers = http_headers || {}
|
|
25
|
+
@json_body = json_body
|
|
26
|
+
@code = code
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_s
|
|
30
|
+
status_string = @http_status.nil? ? '' : "(Status #{@http_status}) "
|
|
31
|
+
"#{status_string}#{@message}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# AuthenticationError is raised when invalid credentials are used to connect
|
|
36
|
+
# to Supercast's servers.
|
|
37
|
+
class AuthenticationError < SupercastError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# APIConnectionError is raised in the event that the SDK can't connect to
|
|
41
|
+
# Supercast's servers. That can be for a variety of different reasons such as a
|
|
42
|
+
# downed network
|
|
43
|
+
class APIConnectionError < SupercastError
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# APIError is a generic error that may be raised in cases where none of the
|
|
47
|
+
# other named errors cover the problem. It could also be raised in the case
|
|
48
|
+
# that a new error has been introduced in the API, but this version of the
|
|
49
|
+
# Ruby SDK doesn't know how to handle it.
|
|
50
|
+
class APIError < SupercastError
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# InvalidRequestError is raised when a request is initiated with invalid
|
|
54
|
+
# parameters.
|
|
55
|
+
class InvalidRequestError < SupercastError
|
|
56
|
+
def initialize(message, http_status: nil, http_body: nil, json_body: nil, http_headers: nil, code: nil)
|
|
57
|
+
super(message, http_status: http_status, http_body: http_body,
|
|
58
|
+
json_body: json_body, http_headers: http_headers,
|
|
59
|
+
code: code)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# PermissionError is raised in cases where access was attempted on a resource
|
|
64
|
+
# that wasn't allowed.
|
|
65
|
+
class PermissionError < SupercastError
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# RateLimitError is raised in cases where an account is putting too much load
|
|
69
|
+
# on Supercast's API servers (usually by performing too many requests). Please
|
|
70
|
+
# back off on request rate.
|
|
71
|
+
class RateLimitError < SupercastError
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
module Operations
|
|
5
|
+
module Create
|
|
6
|
+
# Creates an API resource.
|
|
7
|
+
#
|
|
8
|
+
# ==== Attributes
|
|
9
|
+
#
|
|
10
|
+
# * +params+ - A hash of parameters to pass to the API
|
|
11
|
+
# * +opts+ - A Hash of additional options (separate from the params /
|
|
12
|
+
# object values) to be added to the request.
|
|
13
|
+
def create(params = {}, opts = {})
|
|
14
|
+
resp, opts = request(:post, resource_url, Hash[object_name => params], opts)
|
|
15
|
+
Util.convert_to_supercast_object(resp.data, opts)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
module Operations
|
|
5
|
+
module Destroy
|
|
6
|
+
module ClassMethods
|
|
7
|
+
# Deletes an API resource
|
|
8
|
+
#
|
|
9
|
+
# Deletes the identified resource with the passed in parameters.
|
|
10
|
+
#
|
|
11
|
+
# ==== Attributes
|
|
12
|
+
#
|
|
13
|
+
# * +id+ - ID of the resource to delete.
|
|
14
|
+
# * +params+ - A hash of parameters to pass to the API
|
|
15
|
+
# * +opts+ - A Hash of additional options (separate from the params /
|
|
16
|
+
# object values) to be added to the request.
|
|
17
|
+
def destroy(id, params = {}, opts = {})
|
|
18
|
+
request(:delete, "#{resource_url}/#{id}", params, opts)
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Deletes an API resource instance
|
|
24
|
+
#
|
|
25
|
+
# Deletes the instance resource with the passed in parameters.
|
|
26
|
+
#
|
|
27
|
+
# ==== Attributes
|
|
28
|
+
#
|
|
29
|
+
# * +params+ - A hash of parameters to pass to the API
|
|
30
|
+
# * +opts+ - A Hash of additional options (separate from the params /
|
|
31
|
+
# object values) to be added to the request.
|
|
32
|
+
def destroy(params = {}, opts = {})
|
|
33
|
+
request(:delete, resource_url, params, opts)
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.included(base)
|
|
38
|
+
base.extend(ClassMethods)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
module Operations
|
|
5
|
+
module List
|
|
6
|
+
# Lists an API resource
|
|
7
|
+
#
|
|
8
|
+
# ==== Attributes
|
|
9
|
+
#
|
|
10
|
+
# * +filters+ - A hash of filters to pass to the API
|
|
11
|
+
# * +opts+ - A Hash of additional options (separate from the params /
|
|
12
|
+
# object values) to be added to the request.
|
|
13
|
+
def list(filters = {}, opts = {})
|
|
14
|
+
opts = Util.normalize_opts(opts)
|
|
15
|
+
|
|
16
|
+
resp, opts = request(:get, resource_url, filters, opts)
|
|
17
|
+
obj = DataList.construct_from({
|
|
18
|
+
data: resp.data,
|
|
19
|
+
page: resp.http_headers['x-page'].to_i,
|
|
20
|
+
per_page: resp.http_headers['x-per-page'].to_i,
|
|
21
|
+
total: resp.http_headers['x-total'].to_i
|
|
22
|
+
}, opts)
|
|
23
|
+
|
|
24
|
+
# set filters so that we can fetch the same limit, expansions, and
|
|
25
|
+
# predicates when accessing the next and previous pages
|
|
26
|
+
#
|
|
27
|
+
# just for general cleanliness, remove any paging options
|
|
28
|
+
obj.filters = filters.dup
|
|
29
|
+
obj.filters.delete(:ending_before)
|
|
30
|
+
obj.filters.delete(:starting_after)
|
|
31
|
+
|
|
32
|
+
obj
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Supercast
|
|
4
|
+
module Operations
|
|
5
|
+
module Request
|
|
6
|
+
module ClassMethods
|
|
7
|
+
# Invokes an HTTP request via the Supercast Client for
|
|
8
|
+
# manipulating an API resource.
|
|
9
|
+
#
|
|
10
|
+
# ==== Attributes
|
|
11
|
+
#
|
|
12
|
+
# * +method+ - A symbol for the HTTP verb to use in the request
|
|
13
|
+
# * +url+ - A string which dictates what API endpoint URL to hit
|
|
14
|
+
# * +params+ - A hash of parameters to pass to the API
|
|
15
|
+
# * +opts+ - A Hash of additional options (separate from the params /
|
|
16
|
+
# object values) to be added to the request.
|
|
17
|
+
def request(method, url, params = {}, opts = {})
|
|
18
|
+
warn_on_opts_in_params(params)
|
|
19
|
+
|
|
20
|
+
opts = Util.normalize_opts(opts)
|
|
21
|
+
opts[:client] ||= Client.active_client
|
|
22
|
+
|
|
23
|
+
headers = opts.clone
|
|
24
|
+
api_key = headers.delete(:api_key)
|
|
25
|
+
api_base = headers.delete(:api_base)
|
|
26
|
+
client = headers.delete(:client)
|
|
27
|
+
# Assume all remaining opts must be headers
|
|
28
|
+
|
|
29
|
+
resp, opts[:api_key] = client.execute_request(
|
|
30
|
+
method, url,
|
|
31
|
+
api_base: api_base, api_key: api_key,
|
|
32
|
+
headers: headers, params: params
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Hash#select returns an array before 1.9
|
|
36
|
+
opts_to_persist = {}
|
|
37
|
+
opts.each do |k, v|
|
|
38
|
+
opts_to_persist[k] = v if Util::OPTS_PERSISTABLE.include?(k)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
[resp, opts_to_persist]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def warn_on_opts_in_params(params)
|
|
47
|
+
Util::OPTS_USER_SPECIFIED.each do |opt|
|
|
48
|
+
warn("WARNING: #{opt} should be in opts instead of params.") if params.key?(opt)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.included(base)
|
|
54
|
+
base.extend(ClassMethods)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
def request(method, url, params = {}, opts = {})
|
|
60
|
+
opts = @opts.merge(Util.normalize_opts(opts))
|
|
61
|
+
self.class.request(method, url, params, opts)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|