appnigma-sdk 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +359 -0
- data/lib/appnigma/checks.rb +90 -0
- data/lib/appnigma/client.rb +118 -0
- data/lib/appnigma/connections.rb +31 -0
- data/lib/appnigma/constants.rb +80 -0
- data/lib/appnigma/crm.rb +40 -0
- data/lib/appnigma/errors.rb +135 -0
- data/lib/appnigma/generated/api/default_api.rb +972 -0
- data/lib/appnigma/generated/api_client.rb +441 -0
- data/lib/appnigma/generated/api_error.rb +58 -0
- data/lib/appnigma/generated/api_model_base.rb +88 -0
- data/lib/appnigma/generated/configuration.rb +392 -0
- data/lib/appnigma/generated/models/connection_status.rb +41 -0
- data/lib/appnigma/generated/models/create_connection_link_input.rb +265 -0
- data/lib/appnigma/generated/models/create_connection_link_output.rb +205 -0
- data/lib/appnigma/generated/models/crm_company.rb +322 -0
- data/lib/appnigma/generated/models/crm_company_get_output.rb +177 -0
- data/lib/appnigma/generated/models/crm_company_list_output.rb +192 -0
- data/lib/appnigma/generated/models/crm_contact.rb +404 -0
- data/lib/appnigma/generated/models/crm_contact_get_output.rb +177 -0
- data/lib/appnigma/generated/models/crm_contact_list_output.rb +192 -0
- data/lib/appnigma/generated/models/crm_custom_field.rb +189 -0
- data/lib/appnigma/generated/models/crm_deal.rb +479 -0
- data/lib/appnigma/generated/models/crm_deal_get_output.rb +177 -0
- data/lib/appnigma/generated/models/crm_deal_list_output.rb +192 -0
- data/lib/appnigma/generated/models/crm_email.rb +249 -0
- data/lib/appnigma/generated/models/crm_get_input.rb +272 -0
- data/lib/appnigma/generated/models/crm_labelled.rb +190 -0
- data/lib/appnigma/generated/models/crm_list_input.rb +303 -0
- data/lib/appnigma/generated/models/crm_phone.rb +249 -0
- data/lib/appnigma/generated/models/function_entry.rb +346 -0
- data/lib/appnigma/generated/models/function_owner.rb +105 -0
- data/lib/appnigma/generated/models/function_owner_one_of.rb +201 -0
- data/lib/appnigma/generated/models/function_owner_one_of1.rb +227 -0
- data/lib/appnigma/generated/models/get_connection_output.rb +251 -0
- data/lib/appnigma/generated/models/get_guide_output.rb +178 -0
- data/lib/appnigma/generated/models/json_value.rb +106 -0
- data/lib/appnigma/generated/models/list_functions_output.rb +179 -0
- data/lib/appnigma/generated/models/platform_request_input.rb +314 -0
- data/lib/appnigma/generated/models/platform_request_input_body.rb +104 -0
- data/lib/appnigma/generated/models/platform_request_output.rb +221 -0
- data/lib/appnigma/generated/models/post_input.rb +327 -0
- data/lib/appnigma/generated/models/post_input_record_value.rb +104 -0
- data/lib/appnigma/generated/models/post_output.rb +203 -0
- data/lib/appnigma/generated/models/pull_input.rb +335 -0
- data/lib/appnigma/generated/models/pull_output.rb +191 -0
- data/lib/appnigma/generated/models/pull_output_records_inner.rb +205 -0
- data/lib/appnigma/generated/models/run_input.rb +332 -0
- data/lib/appnigma/generated/models/run_output.rb +164 -0
- data/lib/appnigma/generated/models/sdk_error.rb +177 -0
- data/lib/appnigma/generated/models/sdk_error_error.rb +254 -0
- data/lib/appnigma/generated/models/sdk_error_error_details.rb +211 -0
- data/lib/appnigma/generated/models/sdk_problem.rb +205 -0
- data/lib/appnigma/generated/version.rb +15 -0
- data/lib/appnigma/generated.rb +82 -0
- data/lib/appnigma/names.rb +62 -0
- data/lib/appnigma/retry.rb +80 -0
- data/lib/appnigma/transport.rb +138 -0
- data/lib/appnigma/version.rb +9 -0
- data/lib/appnigma.rb +16 -0
- metadata +155 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
#Appnigma SDK API
|
|
3
|
+
|
|
4
|
+
#The routes a customer's product calls through an Appnigma SDK. Every answer carries a request id in X-Request-Id; quote it when you write to support.
|
|
5
|
+
|
|
6
|
+
The version of the OpenAPI document: 1.0.0
|
|
7
|
+
|
|
8
|
+
Generated by: https://openapi-generator.tech
|
|
9
|
+
Generator version: 7.26.0
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
require 'date'
|
|
14
|
+
require 'time'
|
|
15
|
+
|
|
16
|
+
module Appnigma::Generated
|
|
17
|
+
class SdkProblem < ApiModelBase
|
|
18
|
+
# The field, as a dotted path for a nested one
|
|
19
|
+
attr_accessor :field
|
|
20
|
+
|
|
21
|
+
# What is wrong with it, in plain words
|
|
22
|
+
attr_accessor :problem
|
|
23
|
+
|
|
24
|
+
# Attribute mapping from ruby-style variable name to JSON key.
|
|
25
|
+
def self.attribute_map
|
|
26
|
+
{
|
|
27
|
+
:'field' => :'field',
|
|
28
|
+
:'problem' => :'problem'
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns attribute mapping this model knows about
|
|
33
|
+
def self.acceptable_attribute_map
|
|
34
|
+
attribute_map
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns all the JSON keys this model knows about
|
|
38
|
+
def self.acceptable_attributes
|
|
39
|
+
acceptable_attribute_map.values
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Attribute type mapping.
|
|
43
|
+
def self.openapi_types
|
|
44
|
+
{
|
|
45
|
+
:'field' => :'String',
|
|
46
|
+
:'problem' => :'String'
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# List of attributes with nullable: true
|
|
51
|
+
def self.openapi_nullable
|
|
52
|
+
Set.new([
|
|
53
|
+
])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns attribute type mapping
|
|
57
|
+
def self.acceptable_openapi_types
|
|
58
|
+
openapi_types
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns the nullable attributes
|
|
62
|
+
def self.acceptable_openapi_nullable
|
|
63
|
+
openapi_nullable
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Initializes the object
|
|
67
|
+
# @param [Hash] attributes Model attributes in the form of hash
|
|
68
|
+
def initialize(attributes = {})
|
|
69
|
+
if (!attributes.is_a?(Hash))
|
|
70
|
+
fail ArgumentError, "The input argument (attributes) must be a hash in `Appnigma::Generated::SdkProblem` initialize method"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# check to see if the attribute exists and convert string to symbol for hash key
|
|
74
|
+
acceptable_attribute_map = self.class.acceptable_attribute_map
|
|
75
|
+
attributes = attributes.each_with_object({}) { |(k, v), h|
|
|
76
|
+
if (!acceptable_attribute_map.key?(k.to_sym))
|
|
77
|
+
fail ArgumentError, "`#{k}` is not a valid attribute in `Appnigma::Generated::SdkProblem`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
|
|
78
|
+
end
|
|
79
|
+
h[k.to_sym] = v
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if attributes.key?(:'field')
|
|
83
|
+
self.field = attributes[:'field']
|
|
84
|
+
else
|
|
85
|
+
self.field = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if attributes.key?(:'problem')
|
|
89
|
+
self.problem = attributes[:'problem']
|
|
90
|
+
else
|
|
91
|
+
self.problem = nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Show invalid properties with the reasons. Usually used together with valid?
|
|
96
|
+
# @return Array for valid properties with the reasons
|
|
97
|
+
def list_invalid_properties
|
|
98
|
+
warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
|
|
99
|
+
invalid_properties = Array.new
|
|
100
|
+
if @field.nil?
|
|
101
|
+
invalid_properties.push('invalid value for "field", field cannot be nil.')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if @problem.nil?
|
|
105
|
+
invalid_properties.push('invalid value for "problem", problem cannot be nil.')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
invalid_properties
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Check to see if the all the properties in the model are valid
|
|
112
|
+
# @return true if the model is valid
|
|
113
|
+
def valid?
|
|
114
|
+
warn '[DEPRECATED] the `valid?` method is obsolete'
|
|
115
|
+
return false if @field.nil?
|
|
116
|
+
return false if @problem.nil?
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Custom attribute writer method with validation
|
|
121
|
+
# @param [Object] field Value to be assigned
|
|
122
|
+
def field=(field)
|
|
123
|
+
if field.nil?
|
|
124
|
+
fail ArgumentError, 'field cannot be nil'
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
@field = field
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Custom attribute writer method with validation
|
|
131
|
+
# @param [Object] problem Value to be assigned
|
|
132
|
+
def problem=(problem)
|
|
133
|
+
if problem.nil?
|
|
134
|
+
fail ArgumentError, 'problem cannot be nil'
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@problem = problem
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Checks equality by comparing each attribute.
|
|
141
|
+
# @param [Object] Object to be compared
|
|
142
|
+
def ==(o)
|
|
143
|
+
return true if self.equal?(o)
|
|
144
|
+
self.class == o.class &&
|
|
145
|
+
field == o.field &&
|
|
146
|
+
problem == o.problem
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @see the `==` method
|
|
150
|
+
# @param [Object] Object to be compared
|
|
151
|
+
def eql?(o)
|
|
152
|
+
self == o
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Calculates hash code according to all attributes.
|
|
156
|
+
# @return [Integer] Hash code
|
|
157
|
+
def hash
|
|
158
|
+
[field, problem].hash
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Builds the object from hash
|
|
162
|
+
# @param [Hash] attributes Model attributes in the form of hash
|
|
163
|
+
# @return [Object] Returns the model itself
|
|
164
|
+
def self.build_from_hash(attributes)
|
|
165
|
+
return nil unless attributes.is_a?(Hash)
|
|
166
|
+
attributes = attributes.transform_keys(&:to_sym)
|
|
167
|
+
# include attributes inherited from allOf parents (the nearest declaration wins)
|
|
168
|
+
map = acceptable_attribute_map
|
|
169
|
+
transformed_hash = {}
|
|
170
|
+
acceptable_openapi_types.each_pair do |key, type|
|
|
171
|
+
if attributes.key?(map[key]) && attributes[map[key]].nil?
|
|
172
|
+
transformed_hash["#{key}"] = nil
|
|
173
|
+
elsif type =~ /\AArray<(.*)>/i
|
|
174
|
+
# check to ensure the input is an array given that the attribute
|
|
175
|
+
# is documented as an array but the input is not
|
|
176
|
+
if attributes[map[key]].is_a?(Array)
|
|
177
|
+
transformed_hash["#{key}"] = attributes[map[key]].map { |v| _deserialize($1, v) }
|
|
178
|
+
end
|
|
179
|
+
elsif !attributes[map[key]].nil?
|
|
180
|
+
transformed_hash["#{key}"] = _deserialize(type, attributes[map[key]])
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
new(transformed_hash)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Returns the object in the form of hash
|
|
187
|
+
# @return [Hash] Returns the object in the form of hash
|
|
188
|
+
def to_hash
|
|
189
|
+
hash = {}
|
|
190
|
+
# include attributes inherited from allOf parents (the nearest declaration wins)
|
|
191
|
+
self.class.acceptable_attribute_map.each_pair do |attr, param|
|
|
192
|
+
value = self.send(attr)
|
|
193
|
+
if value.nil?
|
|
194
|
+
is_nullable = self.class.acceptable_openapi_nullable.include?(attr)
|
|
195
|
+
next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
hash[param] = _to_hash(value)
|
|
199
|
+
end
|
|
200
|
+
hash
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
#Appnigma SDK API
|
|
3
|
+
|
|
4
|
+
#The routes a customer's product calls through an Appnigma SDK. Every answer carries a request id in X-Request-Id; quote it when you write to support.
|
|
5
|
+
|
|
6
|
+
The version of the OpenAPI document: 1.0.0
|
|
7
|
+
|
|
8
|
+
Generated by: https://openapi-generator.tech
|
|
9
|
+
Generator version: 7.26.0
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
module Appnigma::Generated
|
|
14
|
+
VERSION = '1.0.0'
|
|
15
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
#Appnigma SDK API
|
|
3
|
+
|
|
4
|
+
#The routes a customer's product calls through an Appnigma SDK. Every answer carries a request id in X-Request-Id; quote it when you write to support.
|
|
5
|
+
|
|
6
|
+
The version of the OpenAPI document: 1.0.0
|
|
7
|
+
|
|
8
|
+
Generated by: https://openapi-generator.tech
|
|
9
|
+
Generator version: 7.26.0
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
# Common files
|
|
14
|
+
require 'appnigma/generated/api_client'
|
|
15
|
+
require 'appnigma/generated/api_error'
|
|
16
|
+
require 'appnigma/generated/api_model_base'
|
|
17
|
+
require 'appnigma/generated/version'
|
|
18
|
+
require 'appnigma/generated/configuration'
|
|
19
|
+
|
|
20
|
+
# Models
|
|
21
|
+
require 'appnigma/generated/models/connection_status'
|
|
22
|
+
require 'appnigma/generated/models/create_connection_link_input'
|
|
23
|
+
require 'appnigma/generated/models/create_connection_link_output'
|
|
24
|
+
require 'appnigma/generated/models/crm_company'
|
|
25
|
+
require 'appnigma/generated/models/crm_company_get_output'
|
|
26
|
+
require 'appnigma/generated/models/crm_company_list_output'
|
|
27
|
+
require 'appnigma/generated/models/crm_contact'
|
|
28
|
+
require 'appnigma/generated/models/crm_contact_get_output'
|
|
29
|
+
require 'appnigma/generated/models/crm_contact_list_output'
|
|
30
|
+
require 'appnigma/generated/models/crm_custom_field'
|
|
31
|
+
require 'appnigma/generated/models/crm_deal'
|
|
32
|
+
require 'appnigma/generated/models/crm_deal_get_output'
|
|
33
|
+
require 'appnigma/generated/models/crm_deal_list_output'
|
|
34
|
+
require 'appnigma/generated/models/crm_email'
|
|
35
|
+
require 'appnigma/generated/models/crm_get_input'
|
|
36
|
+
require 'appnigma/generated/models/crm_labelled'
|
|
37
|
+
require 'appnigma/generated/models/crm_list_input'
|
|
38
|
+
require 'appnigma/generated/models/crm_phone'
|
|
39
|
+
require 'appnigma/generated/models/function_entry'
|
|
40
|
+
require 'appnigma/generated/models/function_owner'
|
|
41
|
+
require 'appnigma/generated/models/function_owner_one_of'
|
|
42
|
+
require 'appnigma/generated/models/function_owner_one_of1'
|
|
43
|
+
require 'appnigma/generated/models/get_connection_output'
|
|
44
|
+
require 'appnigma/generated/models/get_guide_output'
|
|
45
|
+
require 'appnigma/generated/models/json_value'
|
|
46
|
+
require 'appnigma/generated/models/list_functions_output'
|
|
47
|
+
require 'appnigma/generated/models/platform_request_input'
|
|
48
|
+
require 'appnigma/generated/models/platform_request_input_body'
|
|
49
|
+
require 'appnigma/generated/models/platform_request_output'
|
|
50
|
+
require 'appnigma/generated/models/post_input'
|
|
51
|
+
require 'appnigma/generated/models/post_input_record_value'
|
|
52
|
+
require 'appnigma/generated/models/post_output'
|
|
53
|
+
require 'appnigma/generated/models/pull_input'
|
|
54
|
+
require 'appnigma/generated/models/pull_output'
|
|
55
|
+
require 'appnigma/generated/models/pull_output_records_inner'
|
|
56
|
+
require 'appnigma/generated/models/run_input'
|
|
57
|
+
require 'appnigma/generated/models/run_output'
|
|
58
|
+
require 'appnigma/generated/models/sdk_error'
|
|
59
|
+
require 'appnigma/generated/models/sdk_error_error'
|
|
60
|
+
require 'appnigma/generated/models/sdk_error_error_details'
|
|
61
|
+
require 'appnigma/generated/models/sdk_problem'
|
|
62
|
+
|
|
63
|
+
# APIs
|
|
64
|
+
require 'appnigma/generated/api/default_api'
|
|
65
|
+
|
|
66
|
+
module Appnigma::Generated
|
|
67
|
+
class << self
|
|
68
|
+
# Customize default settings for the SDK using block.
|
|
69
|
+
# Appnigma::Generated.configure do |config|
|
|
70
|
+
# config.username = "xxx"
|
|
71
|
+
# config.password = "xxx"
|
|
72
|
+
# end
|
|
73
|
+
# If no block given, return the default Configuration object.
|
|
74
|
+
def configure
|
|
75
|
+
if block_given?
|
|
76
|
+
yield(Configuration.default)
|
|
77
|
+
else
|
|
78
|
+
Configuration.default
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appnigma
|
|
4
|
+
# The wire speaks camelCase; Ruby speaks snake_case. Only the known fields
|
|
5
|
+
# of a call cross this boundary: free-form content (a record, a function's
|
|
6
|
+
# input or output, a CRM record's raw form) passes through untouched.
|
|
7
|
+
module Names
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def to_snake(name)
|
|
11
|
+
name.to_s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_camel(name)
|
|
15
|
+
first, *rest = name.to_s.split("_")
|
|
16
|
+
([first] + rest.map(&:capitalize)).join
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The top level of an answer: known keys become snake_case symbols,
|
|
20
|
+
# values stay as they came. `only` names the keys to convert; the rest
|
|
21
|
+
# are kept under their wire name.
|
|
22
|
+
def snake_keys(hash, only: nil)
|
|
23
|
+
hash.to_h do |key, value|
|
|
24
|
+
convert = only.nil? || only.include?(key.to_s)
|
|
25
|
+
[convert ? to_snake(key).to_sym : key, value]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Wire keys whose value is someone else's content, passed through as it
|
|
30
|
+
# came: a record's properties, a function's output and its schemas, a
|
|
31
|
+
# platform's headers and body, a CRM record's raw form, a custom field's
|
|
32
|
+
# value.
|
|
33
|
+
FREE_FORM = %w[properties output inputSchema outputSchema headers body raw value].freeze
|
|
34
|
+
|
|
35
|
+
# Wire keys whose value is a map keyed by someone else's names: the keys
|
|
36
|
+
# are kept, the values converted.
|
|
37
|
+
KEYED_BY_DATA = %w[customFields].freeze
|
|
38
|
+
|
|
39
|
+
# A whole answer: every key this gem knows becomes a snake_case symbol,
|
|
40
|
+
# at every depth, except inside free-form content.
|
|
41
|
+
def answer(value)
|
|
42
|
+
case value
|
|
43
|
+
when Array then value.map { |item| answer(item) }
|
|
44
|
+
when Hash then value.to_h { |key, item| [to_snake(key).to_sym, answer_field(key, item)] }
|
|
45
|
+
else value
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def answer_field(key, value)
|
|
50
|
+
return value if FREE_FORM.include?(key)
|
|
51
|
+
return value.transform_values { |item| answer(item) } if KEYED_BY_DATA.include?(key) && value.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
answer(value)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The top level of a request: snake_case keyword arguments become the
|
|
57
|
+
# wire's camelCase string keys, and a nil value is left out.
|
|
58
|
+
def camel_keys(hash)
|
|
59
|
+
hash.compact.to_h { |key, value| [to_camel(key), value] }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Appnigma
|
|
6
|
+
# Tries again only when trying again is safe: the network failed, the
|
|
7
|
+
# server answered 5xx, or it answered 429. Never on 400, 401, 403, 404 or
|
|
8
|
+
# 409, because the same request would be refused the same way. The same
|
|
9
|
+
# rules, waits and jitter as the TypeScript package.
|
|
10
|
+
module Retry
|
|
11
|
+
# The wait before each retry when the server sent no Retry-After, with
|
|
12
|
+
# the last one repeated when there are more retries than steps.
|
|
13
|
+
BACKOFF_MS = [500, 1000].freeze
|
|
14
|
+
|
|
15
|
+
# Up to this share of the backoff is added at random, so many clients
|
|
16
|
+
# that failed together do not all try again in the same instant.
|
|
17
|
+
JITTER_SHARE = 0.25
|
|
18
|
+
|
|
19
|
+
# `sleep` takes milliseconds; `random` answers a number in [0, 1). Tests
|
|
20
|
+
# pin them; callers leave them nil for the real ones.
|
|
21
|
+
Policy = Struct.new(:max_retries, :max_retry_wait_ms, :sleep, :random, keyword_init: true)
|
|
22
|
+
|
|
23
|
+
DEFAULT_SLEEP = ->(ms) { Kernel.sleep(ms / 1000.0) }
|
|
24
|
+
DEFAULT_RANDOM = -> { Kernel.rand }
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def retryable_status?(status)
|
|
29
|
+
status == 429 || status >= 500
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Retry-After wins when the server sent one and is taken as given: that
|
|
33
|
+
# wait is the server's instruction. Otherwise the fixed backoff plus
|
|
34
|
+
# jitter.
|
|
35
|
+
def wait_before_retry(answer, retry_index, random)
|
|
36
|
+
return answer[:retry_after_seconds] * 1000 if answer && !answer[:retry_after_seconds].nil?
|
|
37
|
+
|
|
38
|
+
base = BACKOFF_MS[[retry_index, BACKOFF_MS.length - 1].min]
|
|
39
|
+
base + (base * JITTER_SHARE * random.call).floor
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Runs the block up to `max_retries + 1` times. Returns the last answer,
|
|
43
|
+
# good or bad, so the caller judges it; raises only when no answer ever
|
|
44
|
+
# came back. Anything other than a NetworkError is a bug, not a bad
|
|
45
|
+
# second, and is raised at once.
|
|
46
|
+
def with_retries(policy, &block)
|
|
47
|
+
random = policy.random || DEFAULT_RANDOM
|
|
48
|
+
pause = policy.sleep || DEFAULT_SLEEP
|
|
49
|
+
retry_index = 0
|
|
50
|
+
loop do
|
|
51
|
+
answer = attempt(retry_index < policy.max_retries, &block)
|
|
52
|
+
wait_ms = wait_or_stop(answer, retry_index, policy, random)
|
|
53
|
+
return answer if wait_ms.nil?
|
|
54
|
+
|
|
55
|
+
pause.call(wait_ms)
|
|
56
|
+
retry_index += 1
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# How long to wait before the next attempt, or nil to hand this answer
|
|
61
|
+
# back. A nil answer is a network failure with retries left (attempt
|
|
62
|
+
# raises when none are left).
|
|
63
|
+
def wait_or_stop(answer, retry_index, policy, random)
|
|
64
|
+
return wait_before_retry(nil, retry_index, random) if answer.nil?
|
|
65
|
+
return nil if !retryable_status?(answer[:status]) || retry_index >= policy.max_retries
|
|
66
|
+
|
|
67
|
+
wait_ms = wait_before_retry(answer, retry_index, random)
|
|
68
|
+
wait_ms > policy.max_retry_wait_ms ? nil : wait_ms
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# One attempt: the answer, or nil when a network failure may be retried.
|
|
72
|
+
def attempt(retries_left)
|
|
73
|
+
yield
|
|
74
|
+
rescue NetworkError
|
|
75
|
+
raise unless retries_left
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "faraday"
|
|
6
|
+
require_relative "constants"
|
|
7
|
+
require_relative "errors"
|
|
8
|
+
require_relative "version"
|
|
9
|
+
|
|
10
|
+
# The generated client is loaded with Ruby's warnings off. Its models
|
|
11
|
+
# redefine every setter after declaring it, and its API file requires "cgi",
|
|
12
|
+
# which Ruby 4.0 trimmed to the escaping it still uses; both only print
|
|
13
|
+
# under `ruby -w`, and neither is ours to change. The gem's own files load
|
|
14
|
+
# with warnings as usual.
|
|
15
|
+
begin
|
|
16
|
+
verbose = $VERBOSE
|
|
17
|
+
$VERBOSE = nil
|
|
18
|
+
require "appnigma/generated"
|
|
19
|
+
ensure
|
|
20
|
+
$VERBOSE = verbose
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Appnigma
|
|
24
|
+
# One attempt at one call, through the client generated from the API
|
|
25
|
+
# description. The only hand-written file that touches Appnigma::Generated.
|
|
26
|
+
# It reports what came back without judging it: the client decides what
|
|
27
|
+
# an answer means, and Retry decides whether to try again. The only thing
|
|
28
|
+
# raised here is NetworkError: nothing came back.
|
|
29
|
+
#
|
|
30
|
+
# The generated client knows the routes, the path and query values and the
|
|
31
|
+
# key header. It is asked for the body as text, which is parsed here with
|
|
32
|
+
# string keys, so free-form content (a record, a function's output) comes
|
|
33
|
+
# back exactly as the server sent it, and a body that is not JSON is seen
|
|
34
|
+
# as such.
|
|
35
|
+
class Transport
|
|
36
|
+
USER_AGENT = "appnigma-sdk-ruby/#{VERSION}".freeze
|
|
37
|
+
|
|
38
|
+
# Builds the generated API for one configuration. Tests pass their own.
|
|
39
|
+
DEFAULT_API_FACTORY = ->(config) { Generated::DefaultApi.new(Generated::ApiClient.new(config)) }
|
|
40
|
+
|
|
41
|
+
def initialize(base_url:, key:, api_factory: DEFAULT_API_FACTORY)
|
|
42
|
+
@base_url = URI.parse(base_url)
|
|
43
|
+
@key = key
|
|
44
|
+
@api_factory = api_factory
|
|
45
|
+
@apis = {}
|
|
46
|
+
@lock = Mutex.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Calls `<operation>_with_http_info` on the generated API with `args`,
|
|
50
|
+
# within `timeout_ms`. `opts` carries the generated method's optional
|
|
51
|
+
# values (catalog's platform). `header_params` adds request headers (a
|
|
52
|
+
# test uses it to pick a mock server's example). Answers
|
|
53
|
+
# `{ status:, ok:, request_id:, retry_after_seconds:, json: }`, where
|
|
54
|
+
# `json` is nil when the body was empty or not JSON.
|
|
55
|
+
def attempt(operation, *args, timeout_ms:, header_params: {}, opts: {})
|
|
56
|
+
body, status, headers = call_generated(operation, args, timeout_ms, header_params, opts)
|
|
57
|
+
answer(status, headers, body)
|
|
58
|
+
rescue Generated::ApiError => e
|
|
59
|
+
raise network_error(e, timeout_ms) if e.code.nil? || e.code.zero?
|
|
60
|
+
|
|
61
|
+
answer(e.code, e.response_headers, e.response_body)
|
|
62
|
+
rescue Faraday::Error => e
|
|
63
|
+
raise NetworkError.new(message: "Could not reach the Appnigma API: #{e.message}", cause: e)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def call_generated(operation, args, timeout_ms, header_params, opts)
|
|
69
|
+
api_for(timeout_ms).public_send(
|
|
70
|
+
:"#{operation}_with_http_info", *args, **opts, debug_return_type: "String", header_params: header_params.dup
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def api_for(timeout_ms)
|
|
75
|
+
@lock.synchronize { @apis[timeout_ms] ||= @api_factory.call(configuration(timeout_ms)) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def configuration(timeout_ms)
|
|
79
|
+
config = Generated::Configuration.new
|
|
80
|
+
config.scheme = @base_url.scheme
|
|
81
|
+
config.host = @base_url.port == @base_url.default_port ? @base_url.host : "#{@base_url.host}:#{@base_url.port}"
|
|
82
|
+
config.base_path = @base_url.path
|
|
83
|
+
config.access_token = @key
|
|
84
|
+
config.timeout = timeout_ms / 1000.0
|
|
85
|
+
# The server refuses a bad input, with the shape every other refusal
|
|
86
|
+
# has; the generated client's own checks would raise ArgumentError.
|
|
87
|
+
config.client_side_validation = false
|
|
88
|
+
config
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def answer(status, headers, body)
|
|
92
|
+
{
|
|
93
|
+
status: status,
|
|
94
|
+
ok: (200..299).cover?(status),
|
|
95
|
+
request_id: header(headers, REQUEST_ID_HEADER),
|
|
96
|
+
retry_after_seconds: read_retry_after(header(headers, RETRY_AFTER_HEADER)),
|
|
97
|
+
json: parse_json(body)
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def network_error(error, timeout_ms)
|
|
102
|
+
message = if error.message.start_with?("Connection timed out")
|
|
103
|
+
"No answer from the Appnigma API within #{timeout_ms} ms"
|
|
104
|
+
else
|
|
105
|
+
"Could not reach the Appnigma API: #{error.message.lines.first&.strip}"
|
|
106
|
+
end
|
|
107
|
+
NetworkError.new(message: message, cause: error)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Header names are case-insensitive; Faraday's headers already are, a
|
|
111
|
+
# plain Hash is not.
|
|
112
|
+
def header(headers, name)
|
|
113
|
+
return nil if headers.nil?
|
|
114
|
+
|
|
115
|
+
headers.each { |key, value| return value if key.to_s.casecmp?(name) }
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Our server sends seconds. Anything else (an HTTP date from a proxy,
|
|
120
|
+
# junk) reads as "not given" rather than as a wait.
|
|
121
|
+
def read_retry_after(value)
|
|
122
|
+
return nil if value.nil?
|
|
123
|
+
|
|
124
|
+
seconds = Float(value, exception: false)
|
|
125
|
+
return nil if seconds.nil? || !seconds.finite? || seconds.negative?
|
|
126
|
+
|
|
127
|
+
seconds == seconds.floor ? seconds.to_i : seconds
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_json(body)
|
|
131
|
+
return nil if body.nil? || body.to_s.empty?
|
|
132
|
+
|
|
133
|
+
JSON.parse(body.to_s)
|
|
134
|
+
rescue JSON::ParserError
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/appnigma.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "appnigma/version"
|
|
4
|
+
require_relative "appnigma/constants"
|
|
5
|
+
require_relative "appnigma/errors"
|
|
6
|
+
require_relative "appnigma/retry"
|
|
7
|
+
require_relative "appnigma/names"
|
|
8
|
+
require_relative "appnigma/checks"
|
|
9
|
+
require_relative "appnigma/transport"
|
|
10
|
+
require_relative "appnigma/connections"
|
|
11
|
+
require_relative "appnigma/crm"
|
|
12
|
+
require_relative "appnigma/client"
|
|
13
|
+
|
|
14
|
+
# The gem a customer's product installs to reach its Appnigma microserver.
|
|
15
|
+
module Appnigma
|
|
16
|
+
end
|