seatschart 0.2.0 → 0.3.0

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +162 -15
  3. data/lib/seatschart/api/audit_api.rb +2 -2
  4. data/lib/seatschart/api/charts_api.rb +799 -66
  5. data/lib/seatschart/api/embed_api.rb +5 -5
  6. data/lib/seatschart/api/embed_origins_api.rb +63 -2
  7. data/lib/seatschart/api/event_log_api.rb +2 -2
  8. data/lib/seatschart/api/events_api.rb +1471 -155
  9. data/lib/seatschart/api/health_api.rb +244 -0
  10. data/lib/seatschart/api/map_designer_api.rb +11 -9
  11. data/lib/seatschart/api/public_api.rb +350 -0
  12. data/lib/seatschart/api/sandboxes_api.rb +672 -0
  13. data/lib/seatschart/api/seasons_api.rb +344 -36
  14. data/lib/seatschart/api/seats_api.rb +10 -10
  15. data/lib/seatschart/api/usage_api.rb +18 -15
  16. data/lib/seatschart/api/webhooks_api.rb +2 -2
  17. data/lib/seatschart/api/workspace_api.rb +306 -20
  18. data/lib/seatschart/models/api_error.rb +4 -4
  19. data/lib/seatschart/models/delete_api_sandboxes_by_sandbox_id200_response.rb +255 -0
  20. data/lib/seatschart/models/get_api_monitoring_requests200_response.rb +62 -4
  21. data/lib/seatschart/models/get_api_monitoring_requests200_response_points_inner.rb +21 -4
  22. data/lib/seatschart/models/get_api_sandboxes200_response.rb +239 -0
  23. data/lib/seatschart/models/get_api_sandboxes200_response_items_inner.rb +431 -0
  24. data/lib/seatschart/models/get_api_sandboxes200_response_items_inner_rate_limits.rb +270 -0
  25. data/lib/seatschart/models/get_api_sandboxes200_response_items_inner_usage.rb +287 -0
  26. data/lib/seatschart/models/get_api_status200_response.rb +387 -0
  27. data/lib/seatschart/models/get_api_status200_response_components_inner.rb +330 -0
  28. data/lib/seatschart/models/get_api_status200_response_components_inner_days_inner.rb +301 -0
  29. data/lib/seatschart/models/get_api_status200_response_incidents_inner.rb +374 -0
  30. data/lib/seatschart/models/get_api_status200_response_maintenance_inner.rb +350 -0
  31. data/lib/seatschart/models/get_api_status503_response.rb +271 -0
  32. data/lib/seatschart/models/get_api_workspace_buyer_extra_data200_response.rb +241 -0
  33. data/lib/seatschart/models/get_api_workspace_defaults200_response.rb +254 -0
  34. data/lib/seatschart/models/get_api_workspace_defaults200_response_renderer.rb +246 -0
  35. data/lib/seatschart/models/get_api_workspace_defaults200_response_renderer_built_in.rb +270 -0
  36. data/lib/seatschart/models/get_api_workspaces200_response_items_inner.rb +34 -5
  37. data/lib/seatschart/models/patch_api_events_by_event_id_request.rb +16 -6
  38. data/lib/seatschart/models/patch_api_seasons_by_season_id_request.rb +15 -5
  39. data/lib/seatschart/models/patch_api_workspace_chart_validation_request_severities.rb +25 -4
  40. data/lib/seatschart/models/patch_api_workspace_defaults_request.rb +224 -0
  41. data/lib/seatschart/models/patch_api_workspace_defaults_request_renderer.rb +249 -0
  42. data/lib/seatschart/models/patch_api_workspace_settings_request.rb +6 -6
  43. data/lib/seatschart/models/post_api_charts_by_chart_id_actions_copy_to_workspace_request.rb +39 -1
  44. data/lib/seatschart/models/post_api_charts_by_chart_id_actions_validate_request.rb +1 -0
  45. data/lib/seatschart/models/post_api_charts_by_chart_id_map_publish422_response_issues_inner.rb +64 -6
  46. data/lib/seatschart/models/post_api_charts_by_chart_id_map_publish_request.rb +1 -1
  47. data/lib/seatschart/models/post_api_embed_tokens_request.rb +18 -15
  48. data/lib/seatschart/models/post_api_embed_tokens_request_safe_mode_options.rb +226 -0
  49. data/lib/seatschart/models/post_api_events_actions_edit_for_sale_config_request.rb +251 -0
  50. data/lib/seatschart/models/post_api_events_actions_edit_for_sale_config_request_events_inner.rb +322 -0
  51. data/lib/seatschart/models/post_api_events_bulk_request_events_inner.rb +42 -1
  52. data/lib/seatschart/models/post_api_events_by_event_id_actions_edit_for_sale_config_request.rb +277 -0
  53. data/lib/seatschart/models/post_api_events_by_event_id_actions_edit_for_sale_config_request_for_sale_inner.rb +319 -0
  54. data/lib/seatschart/models/post_api_events_by_event_id_map_publish_request.rb +215 -0
  55. data/lib/seatschart/models/post_api_events_request.rb +42 -1
  56. data/lib/seatschart/models/{patch_api_events_by_event_id_request_categories_inner.rb → post_api_events_request_categories_inner.rb} +3 -3
  57. data/lib/seatschart/models/post_api_extract_seats200_response.rb +459 -0
  58. data/lib/seatschart/models/post_api_extract_seats200_response_seats_inner.rb +323 -0
  59. data/lib/seatschart/models/post_api_extract_seats200_response_suggested_inner.rb +320 -0
  60. data/lib/seatschart/models/post_api_extract_seats_request.rb +124 -4
  61. data/lib/seatschart/models/post_api_extract_seats_request_examples.rb +105 -0
  62. data/lib/seatschart/models/post_api_extract_seats_request_examples_any_of_inner.rb +293 -0
  63. data/lib/seatschart/models/post_api_extract_seats_request_negatives.rb +105 -0
  64. data/lib/seatschart/models/post_api_extract_seats_request_region.rb +105 -0
  65. data/lib/seatschart/models/post_api_extract_seats_request_region_any_of.rb +363 -0
  66. data/lib/seatschart/models/post_api_sandboxes201_response.rb +245 -0
  67. data/lib/seatschart/models/post_api_sandboxes201_response_admin_key.rb +286 -0
  68. data/lib/seatschart/models/post_api_sandboxes201_response_copied.rb +313 -0
  69. data/lib/seatschart/models/post_api_sandboxes201_response_copied_charts_inner.rb +281 -0
  70. data/lib/seatschart/models/post_api_sandboxes201_response_copied_events_inner.rb +281 -0
  71. data/lib/seatschart/models/post_api_sandboxes201_response_copied_events_with_own_map_inner.rb +265 -0
  72. data/lib/seatschart/models/post_api_sandboxes201_response_copied_key_conflicts_inner.rb +287 -0
  73. data/lib/seatschart/models/post_api_sandboxes201_response_copied_seasons_inner.rb +312 -0
  74. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_actions_copy_from_production200_response.rb +313 -0
  75. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_actions_copy_from_production_request.rb +255 -0
  76. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_actions_join200_response.rb +271 -0
  77. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_actions_reset200_response.rb +268 -0
  78. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_actions_reset_request.rb +225 -0
  79. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_admin_key201_response.rb +253 -0
  80. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_admin_key201_response_api_key.rb +286 -0
  81. data/lib/seatschart/models/post_api_sandboxes_by_sandbox_id_admin_key_request.rb +252 -0
  82. data/lib/seatschart/models/post_api_sandboxes_request.rb +319 -0
  83. data/lib/seatschart/models/post_api_seasons_request.rb +14 -4
  84. data/lib/seatschart/models/post_api_seats_confirm_request.rb +5 -5
  85. data/lib/seatschart/models/post_api_seats_release_request.rb +5 -5
  86. data/lib/seatschart/models/post_api_seats_reserve_request.rb +5 -5
  87. data/lib/seatschart/models/post_api_workspaces_request.rb +1 -1
  88. data/lib/seatschart/models/put_api_charts_by_chart_id_map_draft_request.rb +1 -0
  89. data/lib/seatschart/models/put_api_workspace_buyer_extra_data_request.rb +260 -0
  90. data/lib/seatschart/models/put_internal_status_notices_request.rb +242 -0
  91. data/lib/seatschart/models/put_internal_status_notices_request_notices_inner.rb +445 -0
  92. data/lib/seatschart/retry.rb +106 -7
  93. data/lib/seatschart/version.rb +1 -1
  94. data/lib/seatschart.rb +53 -2
  95. metadata +55 -4
  96. data/lib/seatschart/api/default_api.rb +0 -763
@@ -0,0 +1,445 @@
1
+ =begin
2
+ #SeatsChart API
3
+
4
+ #Seat inventory and locking API for B2B2C integrations. Authenticate using `x-api-key` (partners) or `Authorization: Bearer` with an HS256 JWT.
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.11.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module SeatsChart
17
+ class PutInternalStatusNoticesRequestNoticesInner
18
+ attr_accessor :id
19
+
20
+ attr_accessor :kind
21
+
22
+ attr_accessor :title
23
+
24
+ attr_accessor :message
25
+
26
+ attr_accessor :impact
27
+
28
+ attr_accessor :components
29
+
30
+ attr_accessor :starts_at
31
+
32
+ attr_accessor :ends_at
33
+
34
+ class EnumAttributeValidator
35
+ attr_reader :datatype
36
+ attr_reader :allowable_values
37
+
38
+ def initialize(datatype, allowable_values)
39
+ @allowable_values = allowable_values.map do |value|
40
+ case datatype.to_s
41
+ when /Integer/i
42
+ value.to_i
43
+ when /Float/i
44
+ value.to_f
45
+ else
46
+ value
47
+ end
48
+ end
49
+ end
50
+
51
+ def valid?(value)
52
+ !value || allowable_values.include?(value)
53
+ end
54
+ end
55
+
56
+ # Attribute mapping from ruby-style variable name to JSON key.
57
+ def self.attribute_map
58
+ {
59
+ :'id' => :'id',
60
+ :'kind' => :'kind',
61
+ :'title' => :'title',
62
+ :'message' => :'message',
63
+ :'impact' => :'impact',
64
+ :'components' => :'components',
65
+ :'starts_at' => :'startsAt',
66
+ :'ends_at' => :'endsAt'
67
+ }
68
+ end
69
+
70
+ # Returns all the JSON keys this model knows about
71
+ def self.acceptable_attributes
72
+ attribute_map.values
73
+ end
74
+
75
+ # Attribute type mapping.
76
+ def self.openapi_types
77
+ {
78
+ :'id' => :'String',
79
+ :'kind' => :'String',
80
+ :'title' => :'String',
81
+ :'message' => :'String',
82
+ :'impact' => :'String',
83
+ :'components' => :'Array<String>',
84
+ :'starts_at' => :'Time',
85
+ :'ends_at' => :'Time'
86
+ }
87
+ end
88
+
89
+ # List of attributes with nullable: true
90
+ def self.openapi_nullable
91
+ Set.new([
92
+ :'ends_at'
93
+ ])
94
+ end
95
+
96
+ # Initializes the object
97
+ # @param [Hash] attributes Model attributes in the form of hash
98
+ def initialize(attributes = {})
99
+ if (!attributes.is_a?(Hash))
100
+ fail ArgumentError, "The input argument (attributes) must be a hash in `SeatsChart::PutInternalStatusNoticesRequestNoticesInner` initialize method"
101
+ end
102
+
103
+ # check to see if the attribute exists and convert string to symbol for hash key
104
+ attributes = attributes.each_with_object({}) { |(k, v), h|
105
+ if (!self.class.attribute_map.key?(k.to_sym))
106
+ fail ArgumentError, "`#{k}` is not a valid attribute in `SeatsChart::PutInternalStatusNoticesRequestNoticesInner`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect
107
+ end
108
+ h[k.to_sym] = v
109
+ }
110
+
111
+ if attributes.key?(:'id')
112
+ self.id = attributes[:'id']
113
+ else
114
+ self.id = nil
115
+ end
116
+
117
+ if attributes.key?(:'kind')
118
+ self.kind = attributes[:'kind']
119
+ else
120
+ self.kind = nil
121
+ end
122
+
123
+ if attributes.key?(:'title')
124
+ self.title = attributes[:'title']
125
+ else
126
+ self.title = nil
127
+ end
128
+
129
+ if attributes.key?(:'message')
130
+ self.message = attributes[:'message']
131
+ end
132
+
133
+ if attributes.key?(:'impact')
134
+ self.impact = attributes[:'impact']
135
+ end
136
+
137
+ if attributes.key?(:'components')
138
+ if (value = attributes[:'components']).is_a?(Array)
139
+ self.components = value
140
+ end
141
+ end
142
+
143
+ if attributes.key?(:'starts_at')
144
+ self.starts_at = attributes[:'starts_at']
145
+ else
146
+ self.starts_at = nil
147
+ end
148
+
149
+ if attributes.key?(:'ends_at')
150
+ self.ends_at = attributes[:'ends_at']
151
+ end
152
+ end
153
+
154
+ # Show invalid properties with the reasons. Usually used together with valid?
155
+ # @return Array for valid properties with the reasons
156
+ def list_invalid_properties
157
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
158
+ invalid_properties = Array.new
159
+ if @id.nil?
160
+ invalid_properties.push('invalid value for "id", id cannot be nil.')
161
+ end
162
+
163
+ if @id.to_s.length > 64
164
+ invalid_properties.push('invalid value for "id", the character length must be smaller than or equal to 64.')
165
+ end
166
+
167
+ if @id.to_s.length < 1
168
+ invalid_properties.push('invalid value for "id", the character length must be great than or equal to 1.')
169
+ end
170
+
171
+ pattern = Regexp.new(/^[A-Za-z0-9_-]+$/)
172
+ if @id !~ pattern
173
+ invalid_properties.push("invalid value for \"id\", must conform to the pattern #{pattern}.")
174
+ end
175
+
176
+ if @kind.nil?
177
+ invalid_properties.push('invalid value for "kind", kind cannot be nil.')
178
+ end
179
+
180
+ if @title.nil?
181
+ invalid_properties.push('invalid value for "title", title cannot be nil.')
182
+ end
183
+
184
+ if @title.to_s.length > 160
185
+ invalid_properties.push('invalid value for "title", the character length must be smaller than or equal to 160.')
186
+ end
187
+
188
+ if @title.to_s.length < 1
189
+ invalid_properties.push('invalid value for "title", the character length must be great than or equal to 1.')
190
+ end
191
+
192
+ if !@message.nil? && @message.to_s.length > 2000
193
+ invalid_properties.push('invalid value for "message", the character length must be smaller than or equal to 2000.')
194
+ end
195
+
196
+ if !@components.nil? && @components.length > 7
197
+ invalid_properties.push('invalid value for "components", number of items must be less than or equal to 7.')
198
+ end
199
+
200
+ if @starts_at.nil?
201
+ invalid_properties.push('invalid value for "starts_at", starts_at cannot be nil.')
202
+ end
203
+
204
+ invalid_properties
205
+ end
206
+
207
+ # Check to see if the all the properties in the model are valid
208
+ # @return true if the model is valid
209
+ def valid?
210
+ warn '[DEPRECATED] the `valid?` method is obsolete'
211
+ return false if @id.nil?
212
+ return false if @id.to_s.length > 64
213
+ return false if @id.to_s.length < 1
214
+ return false if @id !~ Regexp.new(/^[A-Za-z0-9_-]+$/)
215
+ return false if @kind.nil?
216
+ kind_validator = EnumAttributeValidator.new('String', ["incident", "maintenance"])
217
+ return false unless kind_validator.valid?(@kind)
218
+ return false if @title.nil?
219
+ return false if @title.to_s.length > 160
220
+ return false if @title.to_s.length < 1
221
+ return false if !@message.nil? && @message.to_s.length > 2000
222
+ impact_validator = EnumAttributeValidator.new('String', ["none", "minor", "major"])
223
+ return false unless impact_validator.valid?(@impact)
224
+ return false if !@components.nil? && @components.length > 7
225
+ return false if @starts_at.nil?
226
+ true
227
+ end
228
+
229
+ # Custom attribute writer method with validation
230
+ # @param [Object] id Value to be assigned
231
+ def id=(id)
232
+ if id.nil?
233
+ fail ArgumentError, 'id cannot be nil'
234
+ end
235
+
236
+ if id.to_s.length > 64
237
+ fail ArgumentError, 'invalid value for "id", the character length must be smaller than or equal to 64.'
238
+ end
239
+
240
+ if id.to_s.length < 1
241
+ fail ArgumentError, 'invalid value for "id", the character length must be great than or equal to 1.'
242
+ end
243
+
244
+ pattern = Regexp.new(/^[A-Za-z0-9_-]+$/)
245
+ if id !~ pattern
246
+ fail ArgumentError, "invalid value for \"id\", must conform to the pattern #{pattern}."
247
+ end
248
+
249
+ @id = id
250
+ end
251
+
252
+ # Custom attribute writer method checking allowed values (enum).
253
+ # @param [Object] kind Object to be assigned
254
+ def kind=(kind)
255
+ validator = EnumAttributeValidator.new('String', ["incident", "maintenance"])
256
+ unless validator.valid?(kind)
257
+ fail ArgumentError, "invalid value for \"kind\", must be one of #{validator.allowable_values}."
258
+ end
259
+ @kind = kind
260
+ end
261
+
262
+ # Custom attribute writer method with validation
263
+ # @param [Object] title Value to be assigned
264
+ def title=(title)
265
+ if title.nil?
266
+ fail ArgumentError, 'title cannot be nil'
267
+ end
268
+
269
+ if title.to_s.length > 160
270
+ fail ArgumentError, 'invalid value for "title", the character length must be smaller than or equal to 160.'
271
+ end
272
+
273
+ if title.to_s.length < 1
274
+ fail ArgumentError, 'invalid value for "title", the character length must be great than or equal to 1.'
275
+ end
276
+
277
+ @title = title
278
+ end
279
+
280
+ # Custom attribute writer method with validation
281
+ # @param [Object] message Value to be assigned
282
+ def message=(message)
283
+ if message.nil?
284
+ fail ArgumentError, 'message cannot be nil'
285
+ end
286
+
287
+ if message.to_s.length > 2000
288
+ fail ArgumentError, 'invalid value for "message", the character length must be smaller than or equal to 2000.'
289
+ end
290
+
291
+ @message = message
292
+ end
293
+
294
+ # Custom attribute writer method checking allowed values (enum).
295
+ # @param [Object] impact Object to be assigned
296
+ def impact=(impact)
297
+ validator = EnumAttributeValidator.new('String', ["none", "minor", "major"])
298
+ unless validator.valid?(impact)
299
+ fail ArgumentError, "invalid value for \"impact\", must be one of #{validator.allowable_values}."
300
+ end
301
+ @impact = impact
302
+ end
303
+
304
+ # Checks equality by comparing each attribute.
305
+ # @param [Object] Object to be compared
306
+ def ==(o)
307
+ return true if self.equal?(o)
308
+ self.class == o.class &&
309
+ id == o.id &&
310
+ kind == o.kind &&
311
+ title == o.title &&
312
+ message == o.message &&
313
+ impact == o.impact &&
314
+ components == o.components &&
315
+ starts_at == o.starts_at &&
316
+ ends_at == o.ends_at
317
+ end
318
+
319
+ # @see the `==` method
320
+ # @param [Object] Object to be compared
321
+ def eql?(o)
322
+ self == o
323
+ end
324
+
325
+ # Calculates hash code according to all attributes.
326
+ # @return [Integer] Hash code
327
+ def hash
328
+ [id, kind, title, message, impact, components, starts_at, ends_at].hash
329
+ end
330
+
331
+ # Builds the object from hash
332
+ # @param [Hash] attributes Model attributes in the form of hash
333
+ # @return [Object] Returns the model itself
334
+ def self.build_from_hash(attributes)
335
+ return nil unless attributes.is_a?(Hash)
336
+ attributes = attributes.transform_keys(&:to_sym)
337
+ transformed_hash = {}
338
+ openapi_types.each_pair do |key, type|
339
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
340
+ transformed_hash["#{key}"] = nil
341
+ elsif type =~ /\AArray<(.*)>/i
342
+ # check to ensure the input is an array given that the attribute
343
+ # is documented as an array but the input is not
344
+ if attributes[attribute_map[key]].is_a?(Array)
345
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
346
+ end
347
+ elsif !attributes[attribute_map[key]].nil?
348
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
349
+ end
350
+ end
351
+ new(transformed_hash)
352
+ end
353
+
354
+ # Deserializes the data based on type
355
+ # @param string type Data type
356
+ # @param string value Value to be deserialized
357
+ # @return [Object] Deserialized data
358
+ def self._deserialize(type, value)
359
+ case type.to_sym
360
+ when :Time
361
+ Time.parse(value)
362
+ when :Date
363
+ Date.parse(value)
364
+ when :String
365
+ value.to_s
366
+ when :Integer
367
+ value.to_i
368
+ when :Float
369
+ value.to_f
370
+ when :Boolean
371
+ if value.to_s =~ /\A(true|t|yes|y|1)\z/i
372
+ true
373
+ else
374
+ false
375
+ end
376
+ when :Object
377
+ # generic object (usually a Hash), return directly
378
+ value
379
+ when /\AArray<(?<inner_type>.+)>\z/
380
+ inner_type = Regexp.last_match[:inner_type]
381
+ value.map { |v| _deserialize(inner_type, v) }
382
+ when /\AHash<(?<k_type>.+?), (?<v_type>.+)>\z/
383
+ k_type = Regexp.last_match[:k_type]
384
+ v_type = Regexp.last_match[:v_type]
385
+ {}.tap do |hash|
386
+ value.each do |k, v|
387
+ hash[_deserialize(k_type, k)] = _deserialize(v_type, v)
388
+ end
389
+ end
390
+ else # model
391
+ # models (e.g. Pet) or oneOf
392
+ klass = SeatsChart.const_get(type)
393
+ klass.respond_to?(:openapi_any_of) || klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value)
394
+ end
395
+ end
396
+
397
+ # Returns the string representation of the object
398
+ # @return [String] String presentation of the object
399
+ def to_s
400
+ to_hash.to_s
401
+ end
402
+
403
+ # to_body is an alias to to_hash (backward compatibility)
404
+ # @return [Hash] Returns the object in the form of hash
405
+ def to_body
406
+ to_hash
407
+ end
408
+
409
+ # Returns the object in the form of hash
410
+ # @return [Hash] Returns the object in the form of hash
411
+ def to_hash
412
+ hash = {}
413
+ self.class.attribute_map.each_pair do |attr, param|
414
+ value = self.send(attr)
415
+ if value.nil?
416
+ is_nullable = self.class.openapi_nullable.include?(attr)
417
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
418
+ end
419
+
420
+ hash[param] = _to_hash(value)
421
+ end
422
+ hash
423
+ end
424
+
425
+ # Outputs non-array value in the form of hash
426
+ # For object, use to_hash. Otherwise, just return the value
427
+ # @param [Object] value Any valid value
428
+ # @return [Hash] Returns the value in the form of hash
429
+ def _to_hash(value)
430
+ if value.is_a?(Array)
431
+ value.compact.map { |v| _to_hash(v) }
432
+ elsif value.is_a?(Hash)
433
+ {}.tap do |hash|
434
+ value.each { |k, v| hash[k] = _to_hash(v) }
435
+ end
436
+ elsif value.respond_to? :to_hash
437
+ value.to_hash
438
+ else
439
+ value
440
+ end
441
+ end
442
+
443
+ end
444
+
445
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Retry and idempotency helpers for the SeatsChart Ruby client.
3
+ # Retry, timeout and idempotency helpers for the SeatsChart Ruby client.
4
4
  # Copied into the generated gem (lib/seatschart/retry.rb) and required from lib/seatschart.rb by
5
5
  # scripts/generate-server-sdks.sh.
6
6
  #
@@ -9,8 +9,13 @@
9
9
  # OPTIONS, PUT, DELETE) and requests carrying an Idempotency-Key header, which is resent unchanged so the
10
10
  # API replays the stored outcome instead of applying it twice.
11
11
  #
12
+ # Every attempt is bounded, as in seats.io's SDKs: 10 s to connect and 30 s in total (Typhoeus
13
+ # connecttimeout_ms / timeout_ms, set on every request whose configuration has no timeout of its own;
14
+ # nil or 0 disables one). A timed-out attempt is retried like a 503 when it is safe to repeat; the last
15
+ # one raises SeatsChart::Retry::TimeoutError (an ApiError whose error_code is 'timeout').
16
+ #
12
17
  # SeatsChart.configure { |c| c.api_key['x-api-key'] = ENV['SEATSCHART_KEY'] }
13
- # SeatsChart::Retry.policy = SeatsChart::Retry::Policy.new(max_attempts: 5) # optional
18
+ # SeatsChart::Retry.policy = SeatsChart::Retry::Policy.new(max_attempts: 5, timeout: 20) # optional
14
19
  # api.reserve(body, header_params: SeatsChart::Retry.idempotency_headers('order-42'))
15
20
  require 'securerandom'
16
21
  require 'time'
@@ -19,6 +24,54 @@ module SeatsChart
19
24
  module Retry
20
25
  IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key'
21
26
  IDEMPOTENT_METHODS = %w[GET HEAD OPTIONS PUT DELETE].freeze
27
+ DEFAULT_CONNECT_TIMEOUT = 10.0
28
+ DEFAULT_TIMEOUT = 30.0
29
+
30
+ # A request whose last attempt timed out. The outcome on the server is unknown: retry writes with the same
31
+ # Idempotency-Key. An ApiError (when the generated client defines it), so existing rescue clauses still apply.
32
+ class TimeoutError < (SeatsChart.const_defined?(:ApiError) ? SeatsChart::ApiError : StandardError)
33
+ attr_reader :http_method, :path, :timeout, :attempts
34
+
35
+ def initialize(http_method, path, timeout, attempts)
36
+ @http_method = http_method.to_s.upcase
37
+ @path = path
38
+ @timeout = timeout
39
+ @attempts = attempts
40
+ @message = "SeatsChart API request timed out (#{@http_method} #{path}, timeout #{timeout.inspect} s, #{attempts} attempt(s))"
41
+ # Bypass the parent's constructor: its signature belongs to the generated client.
42
+ StandardError.instance_method(:initialize).bind(self).call(@message)
43
+ end
44
+
45
+ # Stable error code.
46
+ def error_code
47
+ 'timeout'
48
+ end
49
+
50
+ def timeout?
51
+ true
52
+ end
53
+
54
+ # No HTTP status: there was no response.
55
+ def code
56
+ nil
57
+ end
58
+
59
+ def response_headers
60
+ {}
61
+ end
62
+
63
+ def response_body
64
+ nil
65
+ end
66
+
67
+ def message
68
+ @message
69
+ end
70
+
71
+ def to_s
72
+ @message
73
+ end
74
+ end
22
75
 
23
76
  # A fresh key for one logical operation; reuse it for every retry of that operation.
24
77
  def self.new_idempotency_key
@@ -35,6 +88,14 @@ module SeatsChart
35
88
  (headers || {}).any? { |name, value| name.to_s.casecmp?('idempotency-key') && !value.to_s.strip.empty? }
36
89
  end
37
90
 
91
+ # True for TimeoutError and for the generated client's timeout ApiError ("Connection timed out", no status).
92
+ def self.timeout_error?(error)
93
+ return true if error.is_a?(TimeoutError)
94
+ return false unless error.respond_to?(:code)
95
+
96
+ (error.code.nil? || error.code.to_i.zero?) && error.message.to_s.match?(/timed out|timeout was reached/i)
97
+ end
98
+
38
99
  # Retry-After in seconds (delta-seconds or HTTP date); nil when absent or invalid.
39
100
  def self.parse_retry_after(value, now = Time.now)
40
101
  return nil if value.nil? || value.to_s.strip.empty?
@@ -56,11 +117,14 @@ module SeatsChart
56
117
  end
57
118
 
58
119
  class Policy
59
- attr_reader :max_attempts, :base_delay, :max_delay, :max_retry_after, :statuses
120
+ attr_reader :max_attempts, :base_delay, :max_delay, :max_retry_after, :statuses, :timeout, :connect_timeout,
121
+ :retry_on_timeout
60
122
 
61
123
  def initialize(max_attempts: 4, base_delay: 0.25, max_delay: 8.0, max_retry_after: 30.0, statuses: [429, 503],
62
- sleeper: ->(seconds) { sleep(seconds) }, random: -> { rand })
124
+ sleeper: ->(seconds) { sleep(seconds) }, random: -> { rand },
125
+ timeout: DEFAULT_TIMEOUT, connect_timeout: DEFAULT_CONNECT_TIMEOUT, retry_on_timeout: true)
63
126
  raise ArgumentError, 'max_attempts must be >= 1' if max_attempts < 1
127
+ raise ArgumentError, 'timeouts must be >= 0' if [timeout, connect_timeout].any? { |t| t && t.negative? }
64
128
 
65
129
  @max_attempts = max_attempts
66
130
  @base_delay = base_delay
@@ -69,6 +133,9 @@ module SeatsChart
69
133
  @statuses = statuses
70
134
  @sleeper = sleeper
71
135
  @random = random
136
+ @timeout = timeout.nil? || timeout.zero? ? nil : timeout
137
+ @connect_timeout = connect_timeout.nil? || connect_timeout.zero? ? nil : connect_timeout
138
+ @retry_on_timeout = retry_on_timeout
72
139
  end
73
140
 
74
141
  # Seconds to wait before retry number attempt (1-based); nil when Retry-After is too long.
@@ -82,6 +149,22 @@ module SeatsChart
82
149
  def wait(seconds)
83
150
  @sleeper.call(seconds)
84
151
  end
152
+
153
+ # Sets the default timeouts on Typhoeus request options that have none (a positive :timeout of the
154
+ # configuration wins). Returns the options.
155
+ def apply_timeouts(options)
156
+ return options unless options.is_a?(Hash)
157
+
158
+ own_total = options[:timeout].to_f.positive? || options.key?(:timeout_ms)
159
+ if timeout && !own_total
160
+ options.delete(:timeout)
161
+ options[:timeout_ms] = (timeout * 1000).round
162
+ end
163
+ if connect_timeout && !options.key?(:connecttimeout) && !options.key?(:connecttimeout_ms)
164
+ options[:connecttimeout_ms] = (connect_timeout * 1000).round
165
+ end
166
+ options
167
+ end
85
168
  end
86
169
 
87
170
  class << self
@@ -92,16 +175,32 @@ module SeatsChart
92
175
  end
93
176
  end
94
177
 
95
- # Prepended to the generated ApiClient: repeats call_api on retryable ApiErrors.
178
+ # Prepended to the generated ApiClient: default timeouts on each request, and call_api repeated on retryable
179
+ # ApiErrors and timed-out attempts.
96
180
  module ApiClientExtension
181
+ def build_request(http_method, path, opts = {})
182
+ request = super
183
+ SeatsChart::Retry.policy.apply_timeouts(request.options) if request.respond_to?(:options)
184
+ request
185
+ end
186
+
97
187
  def call_api(http_method, path, opts = {})
98
188
  policy = SeatsChart::Retry.policy
99
189
  attempt = 1
100
190
  begin
101
191
  super
102
192
  rescue SeatsChart::ApiError => e
103
- raise unless attempt < policy.max_attempts && policy.statuses.include?(e.code.to_i) &&
104
- SeatsChart::Retry.retry_safe?(http_method, opts[:header_params])
193
+ safe = SeatsChart::Retry.retry_safe?(http_method, opts[:header_params])
194
+ if SeatsChart::Retry.timeout_error?(e)
195
+ unless attempt < policy.max_attempts && policy.retry_on_timeout && safe
196
+ raise SeatsChart::Retry::TimeoutError.new(http_method, path, policy.timeout, attempt)
197
+ end
198
+
199
+ policy.wait(policy.delay(attempt, nil))
200
+ attempt += 1
201
+ retry
202
+ end
203
+ raise unless attempt < policy.max_attempts && policy.statuses.include?(e.code.to_i) && safe
105
204
 
106
205
  wait = policy.delay(attempt, SeatsChart::Retry.parse_retry_after(SeatsChart::Retry.header(e.response_headers, 'Retry-After')))
107
206
  raise if wait.nil?
@@ -11,5 +11,5 @@ Generator version: 7.11.0
11
11
  =end
12
12
 
13
13
  module SeatsChart
14
- VERSION = '0.2.0'
14
+ VERSION = '0.3.0'
15
15
  end