Class: Conductor::Http::ApiClient
- Inherits:
-
Object
- Object
- Conductor::Http::ApiClient
- Defined in:
- lib/conductor/http/api_client.rb
Overview
ApiClient handles HTTP communication and serialization/deserialization for Conductor API. It manages authentication tokens with automatic refresh and exponential backoff on failures.
Constant Summary collapse
- PRIMITIVE_TYPES =
[String, Integer, Float, TrueClass, FalseClass, NilClass].freeze
- NATIVE_TYPE_MAPPING =
{ 'String' => String, 'Integer' => Integer, 'Float' => Float, 'Boolean' => :boolean, 'DateTime' => DateTime, 'Date' => Date, 'Time' => Time, 'Object' => Object }.freeze
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
Returns the value of attribute configuration.
-
#default_headers ⇒ Object
Returns the value of attribute default_headers.
-
#last_response ⇒ Object
readonly
Returns the value of attribute last_response.
-
#rest_client ⇒ Object
readonly
Returns the value of attribute rest_client.
Instance Method Summary collapse
-
#call_api(resource_path, method, opts = {}) ⇒ Array, Object
Main API call method with automatic retry on auth failures.
-
#deserialize(response, return_type) ⇒ Object
Deserialize HTTP response body into object.
-
#force_refresh_auth_token ⇒ Boolean
Force refresh authentication token (called on 401/403 errors).
-
#get_authentication_headers ⇒ Hash?
Get authentication headers for requests.
-
#initialize(configuration: nil, default_headers: {}) ⇒ ApiClient
constructor
Initialize ApiClient.
-
#sanitize_for_serialization(obj) ⇒ Object
Sanitize object for serialization to JSON.
Constructor Details
#initialize(configuration: nil, default_headers: {}) ⇒ ApiClient
Initialize ApiClient
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/conductor/http/api_client.rb', line 35 def initialize(configuration: nil, default_headers: {}) @configuration = configuration || Configuration.new @rest_client = RestClient.new(@configuration) @default_headers = get_default_headers.merge(default_headers) # Token refresh backoff tracking @token_refresh_failures = 0 @last_token_refresh_attempt = 0 @max_token_refresh_failures = 5 # Mutex for thread-safe token refresh @token_refresh_mutex = Mutex.new # Initial token fetch refresh_auth_token end |
Instance Attribute Details
#configuration ⇒ Object (readonly)
Returns the value of attribute configuration.
29 30 31 |
# File 'lib/conductor/http/api_client.rb', line 29 def configuration @configuration end |
#default_headers ⇒ Object
Returns the value of attribute default_headers.
30 31 32 |
# File 'lib/conductor/http/api_client.rb', line 30 def default_headers @default_headers end |
#last_response ⇒ Object (readonly)
Returns the value of attribute last_response.
29 30 31 |
# File 'lib/conductor/http/api_client.rb', line 29 def last_response @last_response end |
#rest_client ⇒ Object (readonly)
Returns the value of attribute rest_client.
29 30 31 |
# File 'lib/conductor/http/api_client.rb', line 29 def rest_client @rest_client end |
Instance Method Details
#call_api(resource_path, method, opts = {}) ⇒ Array, Object
Main API call method with automatic retry on auth failures
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/conductor/http/api_client.rb', line 63 def call_api(resource_path, method, opts = {}) call_api_with_retry(resource_path, method, opts) rescue AuthorizationError => e if e.token_expired? || e.invalid_token? token_status = e.token_expired? ? 'expired' : 'invalid' logger.info("Authentication token is #{token_status}, renewing token... (request: #{method} #{resource_path})") if force_refresh_auth_token logger.debug('Authentication token successfully renewed') # Retry the request once after successful token refresh return call_api_no_retry(resource_path, method, opts) else logger.error('Failed to renew authentication token. Please check your credentials.') end end raise end |
#deserialize(response, return_type) ⇒ Object
Deserialize HTTP response body into object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/conductor/http/api_client.rb', line 122 def deserialize(response, return_type) return nil if response.nil? || return_type.nil? body = response.body return nil if body.nil? || body.empty? # For String return type, return the raw body directly # (many Conductor APIs return plain text, e.g. workflow ID) return body.to_s.strip.delete_prefix('"').delete_suffix('"') if return_type == 'String' # Parse response body as JSON for complex types data = response.json if data.nil? # JSON parsing failed — try to use raw body return body end deserialize_data(data, return_type) rescue StandardError => e logger.error("Failed to deserialize data into #{return_type}: #{e.}") nil end |
#force_refresh_auth_token ⇒ Boolean
Force refresh authentication token (called on 401/403 errors)
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/conductor/http/api_client.rb', line 147 def force_refresh_auth_token return false unless @configuration.auth_configured? @token_refresh_mutex.synchronize do # Skip backoff for legitimate token renewal (credentials should be valid) token = get_new_token(skip_backoff: true) if token @configuration.update_token(token) return true end # Check if auth was disabled during token refresh (404 response) unless @configuration.auth_configured? logger.info('Authentication was disabled (no auth endpoint found)') return false end false end end |
#get_authentication_headers ⇒ Hash?
Get authentication headers for requests
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/conductor/http/api_client.rb', line 170 def get_authentication_headers return nil unless @configuration.auth_token now_ms = (Time.now.to_f * 1000).round time_since_last_update = now_ms - @configuration.token_update_time # Proactively refresh token if TTL expired if time_since_last_update > @configuration.auth_token_ttl_msec @token_refresh_mutex.synchronize do logger.info('Authentication token TTL expired, renewing token...') token = get_new_token(skip_backoff: true) @configuration.update_token(token) if token logger.debug('Authentication token successfully renewed') if token end end { 'X-Authorization' => @configuration.auth_token } end |
#sanitize_for_serialization(obj) ⇒ Object
Sanitize object for serialization to JSON
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/conductor/http/api_client.rb', line 84 def sanitize_for_serialization(obj) return nil if obj.nil? return obj if PRIMITIVE_TYPES.any? { |type| obj.is_a?(type) } case obj when Array obj.map { |item| sanitize_for_serialization(item) } when Hash obj.transform_values do |val| sanitize_for_serialization(val) end when DateTime, Date, Time obj.iso8601 else # Handle model objects with ATTRIBUTE_MAP and SWAGGER_TYPES if obj.class.const_defined?(:ATTRIBUTE_MAP) && obj.class.const_defined?(:SWAGGER_TYPES) attr_map = obj.class.const_get(:ATTRIBUTE_MAP) swagger_types = obj.class.const_get(:SWAGGER_TYPES) swagger_types.each_with_object({}) do |(attr, _type), hash| value = obj.send(attr) next if value.nil? json_key = attr_map[attr] hash[json_key] = sanitize_for_serialization(value) end elsif obj.respond_to?(:to_h) sanitize_for_serialization(obj.to_h) else obj.to_s end end end |