conductor_ruby 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.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/LICENSE +190 -0
  4. data/README.md +517 -0
  5. data/examples/agentic_workflows/llm_chat.rb +106 -0
  6. data/examples/dynamic_workflow.rb +177 -0
  7. data/examples/event_handler.rb +94 -0
  8. data/examples/event_listener_examples.rb +430 -0
  9. data/examples/helloworld/greetings_worker.rb +24 -0
  10. data/examples/helloworld/helloworld.rb +99 -0
  11. data/examples/kitchensink.rb +213 -0
  12. data/examples/metadata_journey.rb +189 -0
  13. data/examples/metrics_example.rb +284 -0
  14. data/examples/new_dsl_demo.rb +141 -0
  15. data/examples/orkes/http_poll.rb +83 -0
  16. data/examples/orkes/secrets_example.rb +69 -0
  17. data/examples/orkes/wait_for_webhook.rb +90 -0
  18. data/examples/prompt_journey.rb +245 -0
  19. data/examples/rag_workflow.rb +167 -0
  20. data/examples/schedule_journey.rb +244 -0
  21. data/examples/simple_worker.rb +125 -0
  22. data/examples/simple_workflow.rb +89 -0
  23. data/examples/task_context_example.rb +257 -0
  24. data/examples/task_listener_example.rb +192 -0
  25. data/examples/worker_configuration_example.rb +282 -0
  26. data/examples/workflow_dsl.rb +316 -0
  27. data/examples/workflow_ops.rb +305 -0
  28. data/lib/conductor/client/authorization_client.rb +238 -0
  29. data/lib/conductor/client/integration_client.rb +108 -0
  30. data/lib/conductor/client/metadata_client.rb +139 -0
  31. data/lib/conductor/client/prompt_client.rb +58 -0
  32. data/lib/conductor/client/scheduler_client.rb +132 -0
  33. data/lib/conductor/client/schema_client.rb +32 -0
  34. data/lib/conductor/client/secret_client.rb +48 -0
  35. data/lib/conductor/client/task_client.rb +168 -0
  36. data/lib/conductor/client/workflow_client.rb +242 -0
  37. data/lib/conductor/configuration/authentication_settings.rb +17 -0
  38. data/lib/conductor/configuration.rb +103 -0
  39. data/lib/conductor/exceptions.rb +86 -0
  40. data/lib/conductor/http/api/application_resource_api.rb +107 -0
  41. data/lib/conductor/http/api/authorization_resource_api.rb +56 -0
  42. data/lib/conductor/http/api/event_resource_api.rb +133 -0
  43. data/lib/conductor/http/api/gateway_auth_resource_api.rb +48 -0
  44. data/lib/conductor/http/api/group_resource_api.rb +76 -0
  45. data/lib/conductor/http/api/integration_resource_api.rb +145 -0
  46. data/lib/conductor/http/api/metadata_resource_api.rb +231 -0
  47. data/lib/conductor/http/api/prompt_resource_api.rb +81 -0
  48. data/lib/conductor/http/api/role_resource_api.rb +60 -0
  49. data/lib/conductor/http/api/scheduler_resource_api.rb +211 -0
  50. data/lib/conductor/http/api/schema_resource_api.rb +82 -0
  51. data/lib/conductor/http/api/secret_resource_api.rb +134 -0
  52. data/lib/conductor/http/api/task_resource_api.rb +321 -0
  53. data/lib/conductor/http/api/token_resource_api.rb +42 -0
  54. data/lib/conductor/http/api/user_resource_api.rb +59 -0
  55. data/lib/conductor/http/api/workflow_bulk_resource_api.rb +91 -0
  56. data/lib/conductor/http/api/workflow_resource_api.rb +451 -0
  57. data/lib/conductor/http/api_client.rb +437 -0
  58. data/lib/conductor/http/models/authentication_config.rb +67 -0
  59. data/lib/conductor/http/models/authorization_request.rb +39 -0
  60. data/lib/conductor/http/models/base_model.rb +162 -0
  61. data/lib/conductor/http/models/bulk_response.rb +39 -0
  62. data/lib/conductor/http/models/conductor_application.rb +39 -0
  63. data/lib/conductor/http/models/conductor_user.rb +53 -0
  64. data/lib/conductor/http/models/create_or_update_application_request.rb +24 -0
  65. data/lib/conductor/http/models/create_or_update_role_request.rb +27 -0
  66. data/lib/conductor/http/models/event_handler.rb +130 -0
  67. data/lib/conductor/http/models/generate_token_request.rb +27 -0
  68. data/lib/conductor/http/models/group.rb +36 -0
  69. data/lib/conductor/http/models/integration.rb +70 -0
  70. data/lib/conductor/http/models/integration_api.rb +53 -0
  71. data/lib/conductor/http/models/integration_api_update.rb +43 -0
  72. data/lib/conductor/http/models/integration_update.rb +36 -0
  73. data/lib/conductor/http/models/permission.rb +24 -0
  74. data/lib/conductor/http/models/poll_data.rb +33 -0
  75. data/lib/conductor/http/models/prompt_template.rb +59 -0
  76. data/lib/conductor/http/models/prompt_template_test_request.rb +43 -0
  77. data/lib/conductor/http/models/rerun_workflow_request.rb +37 -0
  78. data/lib/conductor/http/models/role.rb +27 -0
  79. data/lib/conductor/http/models/schema_def.rb +59 -0
  80. data/lib/conductor/http/models/search_result.rb +187 -0
  81. data/lib/conductor/http/models/skip_task_request.rb +27 -0
  82. data/lib/conductor/http/models/start_workflow_request.rb +68 -0
  83. data/lib/conductor/http/models/subject_ref.rb +35 -0
  84. data/lib/conductor/http/models/tag_object.rb +36 -0
  85. data/lib/conductor/http/models/target_ref.rb +39 -0
  86. data/lib/conductor/http/models/task.rb +156 -0
  87. data/lib/conductor/http/models/task_def.rb +95 -0
  88. data/lib/conductor/http/models/task_exec_log.rb +30 -0
  89. data/lib/conductor/http/models/task_result.rb +115 -0
  90. data/lib/conductor/http/models/task_result_status.rb +24 -0
  91. data/lib/conductor/http/models/token.rb +33 -0
  92. data/lib/conductor/http/models/upsert_group_request.rb +30 -0
  93. data/lib/conductor/http/models/upsert_user_request.rb +39 -0
  94. data/lib/conductor/http/models/workflow.rb +202 -0
  95. data/lib/conductor/http/models/workflow_def.rb +73 -0
  96. data/lib/conductor/http/models/workflow_schedule.rb +100 -0
  97. data/lib/conductor/http/models/workflow_state_update.rb +30 -0
  98. data/lib/conductor/http/models/workflow_status_constants.rb +57 -0
  99. data/lib/conductor/http/models/workflow_task.rb +169 -0
  100. data/lib/conductor/http/models/workflow_test_request.rb +67 -0
  101. data/lib/conductor/http/rest_client.rb +211 -0
  102. data/lib/conductor/orkes/models/access_key.rb +56 -0
  103. data/lib/conductor/orkes/models/granted_permission.rb +27 -0
  104. data/lib/conductor/orkes/models/metadata_tag.rb +15 -0
  105. data/lib/conductor/orkes/models/rate_limit_tag.rb +15 -0
  106. data/lib/conductor/orkes/orkes_clients.rb +69 -0
  107. data/lib/conductor/version.rb +5 -0
  108. data/lib/conductor/worker/events/conductor_event.rb +40 -0
  109. data/lib/conductor/worker/events/global_dispatcher.rb +37 -0
  110. data/lib/conductor/worker/events/http_events.rb +25 -0
  111. data/lib/conductor/worker/events/listener_registry.rb +40 -0
  112. data/lib/conductor/worker/events/listeners.rb +34 -0
  113. data/lib/conductor/worker/events/sync_event_dispatcher.rb +78 -0
  114. data/lib/conductor/worker/events/task_runner_events.rb +271 -0
  115. data/lib/conductor/worker/events/workflow_events.rb +49 -0
  116. data/lib/conductor/worker/fiber_executor.rb +532 -0
  117. data/lib/conductor/worker/ractor_task_runner.rb +501 -0
  118. data/lib/conductor/worker/task_context.rb +114 -0
  119. data/lib/conductor/worker/task_definition_registrar.rb +322 -0
  120. data/lib/conductor/worker/task_handler.rb +360 -0
  121. data/lib/conductor/worker/task_in_progress.rb +60 -0
  122. data/lib/conductor/worker/task_runner.rb +538 -0
  123. data/lib/conductor/worker/telemetry/metrics_collector.rb +196 -0
  124. data/lib/conductor/worker/telemetry/prometheus_backend.rb +224 -0
  125. data/lib/conductor/worker/worker.rb +355 -0
  126. data/lib/conductor/worker/worker_config.rb +154 -0
  127. data/lib/conductor/worker/worker_registry.rb +71 -0
  128. data/lib/conductor/workflow/dsl/input_ref.rb +37 -0
  129. data/lib/conductor/workflow/dsl/output_ref.rb +44 -0
  130. data/lib/conductor/workflow/dsl/parallel_builder.rb +49 -0
  131. data/lib/conductor/workflow/dsl/switch_builder.rb +74 -0
  132. data/lib/conductor/workflow/dsl/task_ref.rb +178 -0
  133. data/lib/conductor/workflow/dsl/workflow_builder.rb +1016 -0
  134. data/lib/conductor/workflow/dsl/workflow_definition.rb +150 -0
  135. data/lib/conductor/workflow/llm/chat_message.rb +47 -0
  136. data/lib/conductor/workflow/llm/embedding_model.rb +19 -0
  137. data/lib/conductor/workflow/llm/tool_call.rb +43 -0
  138. data/lib/conductor/workflow/llm/tool_spec.rb +46 -0
  139. data/lib/conductor/workflow/task_type.rb +68 -0
  140. data/lib/conductor/workflow/timeout_policy.rb +31 -0
  141. data/lib/conductor/workflow/workflow_executor.rb +373 -0
  142. data/lib/conductor.rb +192 -0
  143. metadata +359 -0
@@ -0,0 +1,437 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'time'
5
+ require 'base64'
6
+ require 'uri'
7
+ require_relative '../configuration'
8
+ require_relative '../exceptions'
9
+ require_relative 'rest_client'
10
+ require_relative 'models/token'
11
+
12
+ module Conductor
13
+ module Http
14
+ # ApiClient handles HTTP communication and serialization/deserialization for Conductor API.
15
+ # It manages authentication tokens with automatic refresh and exponential backoff on failures.
16
+ class ApiClient
17
+ PRIMITIVE_TYPES = [String, Integer, Float, TrueClass, FalseClass, NilClass].freeze
18
+ NATIVE_TYPE_MAPPING = {
19
+ 'String' => String,
20
+ 'Integer' => Integer,
21
+ 'Float' => Float,
22
+ 'Boolean' => :boolean,
23
+ 'DateTime' => DateTime,
24
+ 'Date' => Date,
25
+ 'Time' => Time,
26
+ 'Object' => Object
27
+ }.freeze
28
+
29
+ attr_reader :configuration, :rest_client, :last_response
30
+ attr_accessor :default_headers
31
+
32
+ # Initialize ApiClient
33
+ # @param [Configuration] configuration Configuration object
34
+ # @param [Hash] default_headers Optional default headers
35
+ def initialize(configuration: nil, default_headers: {})
36
+ @configuration = configuration || Configuration.new
37
+ @rest_client = RestClient.new(@configuration)
38
+ @default_headers = get_default_headers.merge(default_headers)
39
+
40
+ # Token refresh backoff tracking
41
+ @token_refresh_failures = 0
42
+ @last_token_refresh_attempt = 0
43
+ @max_token_refresh_failures = 5
44
+
45
+ # Mutex for thread-safe token refresh
46
+ @token_refresh_mutex = Mutex.new
47
+
48
+ # Initial token fetch
49
+ refresh_auth_token
50
+ end
51
+
52
+ # Main API call method with automatic retry on auth failures
53
+ # @param [String] resource_path The resource path
54
+ # @param [String] method HTTP method (GET, POST, PUT, DELETE, PATCH)
55
+ # @param [Hash] opts Optional parameters
56
+ # @option opts [Hash] :path_params Path parameters
57
+ # @option opts [Hash] :query_params Query parameters
58
+ # @option opts [Hash] :header_params Header parameters
59
+ # @option opts [Object] :body Request body
60
+ # @option opts [String] :return_type Expected return type
61
+ # @option opts [Boolean] :return_http_data_only Return only data (default: false)
62
+ # @return [Array, Object] Response data (and status/headers if return_http_data_only is false)
63
+ def call_api(resource_path, method, opts = {})
64
+ call_api_with_retry(resource_path, method, opts)
65
+ rescue AuthorizationError => e
66
+ if e.token_expired? || e.invalid_token?
67
+ token_status = e.token_expired? ? 'expired' : 'invalid'
68
+ logger.info("Authentication token is #{token_status}, renewing token... (request: #{method} #{resource_path})")
69
+
70
+ if force_refresh_auth_token
71
+ logger.debug('Authentication token successfully renewed')
72
+ # Retry the request once after successful token refresh
73
+ return call_api_no_retry(resource_path, method, opts)
74
+ else
75
+ logger.error('Failed to renew authentication token. Please check your credentials.')
76
+ end
77
+ end
78
+ raise
79
+ end
80
+
81
+ # Sanitize object for serialization to JSON
82
+ # @param [Object] obj Object to sanitize
83
+ # @return [Object] Sanitized object ready for JSON serialization
84
+ def sanitize_for_serialization(obj)
85
+ return nil if obj.nil?
86
+ return obj if PRIMITIVE_TYPES.any? { |type| obj.is_a?(type) }
87
+
88
+ case obj
89
+ when Array
90
+ obj.map { |item| sanitize_for_serialization(item) }
91
+ when Hash
92
+ obj.transform_values do |val|
93
+ sanitize_for_serialization(val)
94
+ end
95
+ when DateTime, Date, Time
96
+ obj.iso8601
97
+ else
98
+ # Handle model objects with ATTRIBUTE_MAP and SWAGGER_TYPES
99
+ if obj.class.const_defined?(:ATTRIBUTE_MAP) && obj.class.const_defined?(:SWAGGER_TYPES)
100
+ attr_map = obj.class.const_get(:ATTRIBUTE_MAP)
101
+ swagger_types = obj.class.const_get(:SWAGGER_TYPES)
102
+
103
+ swagger_types.each_with_object({}) do |(attr, _type), hash|
104
+ value = obj.send(attr)
105
+ next if value.nil?
106
+
107
+ json_key = attr_map[attr]
108
+ hash[json_key] = sanitize_for_serialization(value)
109
+ end
110
+ elsif obj.respond_to?(:to_h)
111
+ sanitize_for_serialization(obj.to_h)
112
+ else
113
+ obj.to_s
114
+ end
115
+ end
116
+ end
117
+
118
+ # Deserialize HTTP response body into object
119
+ # @param [RestResponse] response HTTP response
120
+ # @param [String] return_type Expected return type (e.g., 'String', 'Array<Task>', 'Hash<String, Object>')
121
+ # @return [Object] Deserialized object
122
+ def deserialize(response, return_type)
123
+ return nil if response.nil? || return_type.nil?
124
+
125
+ body = response.body
126
+ return nil if body.nil? || body.empty?
127
+
128
+ # For String return type, return the raw body directly
129
+ # (many Conductor APIs return plain text, e.g. workflow ID)
130
+ return body.to_s.strip.delete_prefix('"').delete_suffix('"') if return_type == 'String'
131
+
132
+ # Parse response body as JSON for complex types
133
+ data = response.json
134
+ if data.nil?
135
+ # JSON parsing failed — try to use raw body
136
+ return body
137
+ end
138
+
139
+ deserialize_data(data, return_type)
140
+ rescue StandardError => e
141
+ logger.error("Failed to deserialize data into #{return_type}: #{e.message}")
142
+ nil
143
+ end
144
+
145
+ # Force refresh authentication token (called on 401/403 errors)
146
+ # @return [Boolean] true if token was successfully refreshed, false otherwise
147
+ def force_refresh_auth_token
148
+ return false unless @configuration.auth_configured?
149
+
150
+ @token_refresh_mutex.synchronize do
151
+ # Skip backoff for legitimate token renewal (credentials should be valid)
152
+ token = get_new_token(skip_backoff: true)
153
+ if token
154
+ @configuration.update_token(token)
155
+ return true
156
+ end
157
+
158
+ # Check if auth was disabled during token refresh (404 response)
159
+ unless @configuration.auth_configured?
160
+ logger.info('Authentication was disabled (no auth endpoint found)')
161
+ return false
162
+ end
163
+
164
+ false
165
+ end
166
+ end
167
+
168
+ # Get authentication headers for requests
169
+ # @return [Hash, nil] Headers hash with X-Authorization or nil
170
+ def get_authentication_headers
171
+ return nil unless @configuration.auth_token
172
+
173
+ now_ms = (Time.now.to_f * 1000).round
174
+ time_since_last_update = now_ms - @configuration.token_update_time
175
+
176
+ # Proactively refresh token if TTL expired
177
+ if time_since_last_update > @configuration.auth_token_ttl_msec
178
+ @token_refresh_mutex.synchronize do
179
+ logger.info('Authentication token TTL expired, renewing token...')
180
+ token = get_new_token(skip_backoff: true)
181
+ @configuration.update_token(token) if token
182
+ logger.debug('Authentication token successfully renewed') if token
183
+ end
184
+ end
185
+
186
+ { 'X-Authorization' => @configuration.auth_token }
187
+ end
188
+
189
+ private
190
+
191
+ # Call API without automatic retry (internal method)
192
+ def call_api_no_retry(resource_path, method, opts = {})
193
+ path_params = opts[:path_params] || {}
194
+ query_params = opts[:query_params] || {}
195
+ header_params = (opts[:header_params] || {}).merge(@default_headers)
196
+ body = opts[:body]
197
+ return_type = opts[:return_type]
198
+ return_http_data_only = opts[:return_http_data_only] || false
199
+
200
+ metric_uri = resource_path
201
+
202
+ # Replace path parameters
203
+ path_params.each do |key, value|
204
+ resource_path = resource_path.sub("{#{key}}", URI.encode_www_form_component(value.to_s))
205
+ end
206
+
207
+ # Add authentication headers (skip for /token endpoint)
208
+ if @configuration.auth_configured? && resource_path != '/token'
209
+ auth_headers = get_authentication_headers
210
+ header_params.merge!(auth_headers) if auth_headers
211
+ end
212
+
213
+ # Sanitize body for serialization
214
+ body = sanitize_for_serialization(body) if body
215
+
216
+ # Build full URL
217
+ url = @configuration.server_url + resource_path
218
+
219
+ # Make HTTP request
220
+ response = @rest_client.request(
221
+ method.to_s.upcase,
222
+ url,
223
+ query: query_params,
224
+ headers: header_params,
225
+ body: body ? JSON.generate(body) : nil,
226
+ metric_uri: metric_uri
227
+ )
228
+
229
+ @last_response = response
230
+
231
+ # Deserialize response
232
+ return_data = return_type ? deserialize(response, return_type) : nil
233
+
234
+ if return_http_data_only
235
+ return_data
236
+ else
237
+ [return_data, response.status, response.headers]
238
+ end
239
+ end
240
+
241
+ # Wrapper to handle auth retry
242
+ alias call_api_with_retry call_api_no_retry
243
+
244
+ # Refresh authentication token on initialization
245
+ def refresh_auth_token
246
+ return if @configuration.auth_token
247
+ return unless @configuration.auth_configured?
248
+
249
+ @token_refresh_mutex.synchronize do
250
+ token = get_new_token(skip_backoff: false)
251
+ @configuration.update_token(token) if token || @configuration.auth_configured?
252
+ end
253
+ end
254
+
255
+ # Get new token from server with exponential backoff
256
+ # @param [Boolean] skip_backoff Skip backoff logic for legitimate renewals
257
+ # @return [String, nil] Token string or nil
258
+ def get_new_token(skip_backoff: false)
259
+ # Apply backoff only if not skipping and we have failures
260
+ unless skip_backoff
261
+ if @token_refresh_failures >= @max_token_refresh_failures
262
+ logger.error(
263
+ "Token refresh has failed #{@token_refresh_failures} times. " \
264
+ 'Please check your authentication credentials. ' \
265
+ 'Stopping token refresh attempts.'
266
+ )
267
+ return nil
268
+ end
269
+
270
+ # Exponential backoff: 2^failures seconds
271
+ if @token_refresh_failures.positive?
272
+ now = Time.now.to_f
273
+ backoff_seconds = 2**@token_refresh_failures
274
+ time_since_last_attempt = now - @last_token_refresh_attempt
275
+
276
+ if time_since_last_attempt < backoff_seconds
277
+ remaining = backoff_seconds - time_since_last_attempt
278
+ logger.warn(
279
+ "Token refresh backoff active. Please wait #{remaining.round(1)}s before next attempt. " \
280
+ "(Failure count: #{@token_refresh_failures})"
281
+ )
282
+ return nil
283
+ end
284
+ end
285
+ end
286
+
287
+ @last_token_refresh_attempt = Time.now.to_f
288
+
289
+ begin
290
+ key_id = @configuration.authentication_settings.key_id
291
+ key_secret = @configuration.authentication_settings.key_secret
292
+
293
+ if key_id.nil? || key_secret.nil?
294
+ logger.error('Authentication Key or Secret is not set. Failed to get the auth token')
295
+ @token_refresh_failures += 1
296
+ return nil
297
+ end
298
+
299
+ logger.debug('Requesting new authentication token from server')
300
+
301
+ response = call_api_no_retry(
302
+ '/token',
303
+ 'POST',
304
+ header_params: { 'Content-Type' => 'application/json' },
305
+ body: { keyId: key_id, keySecret: key_secret },
306
+ return_type: 'Token',
307
+ return_http_data_only: true
308
+ )
309
+
310
+ # Success - reset failure counter
311
+ @token_refresh_failures = 0
312
+ response.token
313
+ rescue AuthorizationError => e
314
+ # 401 from /token endpoint - invalid credentials
315
+ @token_refresh_failures += 1
316
+ logger.error(
317
+ "Authentication failed when getting token (attempt #{@token_refresh_failures}): " \
318
+ "#{e.status} - #{e.code}. " \
319
+ 'Please check your CONDUCTOR_AUTH_KEY and CONDUCTOR_AUTH_SECRET. ' \
320
+ "Will retry with exponential backoff (#{2**@token_refresh_failures}s)."
321
+ )
322
+ nil
323
+ rescue ApiError => e
324
+ # Check if it's a 404 - indicates no authentication endpoint (Conductor OSS)
325
+ if e.not_found?
326
+ logger.info(
327
+ 'Authentication endpoint /token not found (404). ' \
328
+ 'Running in open mode without authentication (Conductor OSS).'
329
+ )
330
+ # Disable authentication to prevent future attempts
331
+ @configuration.disable_auth!
332
+ # Reset failure counter since this is not a failure
333
+ @token_refresh_failures = 0
334
+ else
335
+ # Other API errors
336
+ @token_refresh_failures += 1
337
+ logger.error(
338
+ "API error when getting token (attempt #{@token_refresh_failures}): " \
339
+ "#{e.status} - #{e.reason}"
340
+ )
341
+ end
342
+ nil
343
+ rescue StandardError => e
344
+ # Other errors (network, etc)
345
+ @token_refresh_failures += 1
346
+ logger.error("Failed to get new token (attempt #{@token_refresh_failures}): #{e.message}")
347
+ nil
348
+ end
349
+ end
350
+
351
+ # Deserialize data into specified type
352
+ # @param [Object] data Data to deserialize
353
+ # @param [String] type_string Type string (e.g., 'Task', 'Array<Task>', 'Hash<String, Integer>')
354
+ # @return [Object] Deserialized object
355
+ def deserialize_data(data, type_string)
356
+ return nil if data.nil?
357
+
358
+ # Handle Array types: Array<Type>
359
+ if type_string.start_with?('Array<')
360
+ sub_type = type_string[6..-2] # Extract Type from Array<Type>
361
+ return data.map { |item| deserialize_data(item, sub_type) } if data.is_a?(Array)
362
+ end
363
+
364
+ # Handle Hash types: Hash<KeyType, ValueType>
365
+ if type_string.start_with?('Hash<')
366
+ match = type_string.match(/Hash<([^,]+),\s*(.+)>/)
367
+ if match
368
+ _key_type = match[1]
369
+ value_type = match[2]
370
+ return data.transform_values { |v| deserialize_data(v, value_type) } if data.is_a?(Hash)
371
+ end
372
+ end
373
+
374
+ # Handle primitive types
375
+ case type_string
376
+ when 'String'
377
+ data.to_s
378
+ when 'Integer'
379
+ data.to_i
380
+ when 'Float'
381
+ data.to_f
382
+ when 'Boolean'
383
+ data == true || data.to_s.downcase == 'true'
384
+ when 'DateTime'
385
+ DateTime.parse(data.to_s)
386
+ when 'Date'
387
+ Date.parse(data.to_s)
388
+ when 'Time'
389
+ Time.parse(data.to_s)
390
+ when 'Object'
391
+ data
392
+ else
393
+ # Try to deserialize as a model class
394
+ deserialize_model(data, type_string)
395
+ end
396
+ end
397
+
398
+ # Deserialize data into model class
399
+ # @param [Hash] data Data hash
400
+ # @param [String] class_name Model class name
401
+ # @return [Object] Model instance
402
+ def deserialize_model(data, class_name)
403
+ return data unless data.is_a?(Hash)
404
+
405
+ # Try to get the model class from Conductor::Http::Models
406
+ klass = begin
407
+ Conductor::Http::Models.const_get(class_name)
408
+ rescue NameError
409
+ return data
410
+ end
411
+
412
+ # Use BaseModel.from_hash if available
413
+ if klass.respond_to?(:from_hash)
414
+ klass.from_hash(data)
415
+ else
416
+ data
417
+ end
418
+ end
419
+
420
+ # Get default headers
421
+ def get_default_headers
422
+ {
423
+ 'Content-Type' => 'application/json',
424
+ 'Accept' => 'application/json'
425
+ }
426
+ end
427
+
428
+ # Get logger
429
+ def logger
430
+ @logger ||= begin
431
+ require 'logger'
432
+ Logger.new($stdout, level: Logger::INFO)
433
+ end
434
+ end
435
+ end
436
+ end
437
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conductor
4
+ module Http
5
+ module Models
6
+ # Authentication type constants
7
+ module AuthenticationType
8
+ NONE = 'NONE'
9
+ API_KEY = 'API_KEY'
10
+ OIDC = 'OIDC'
11
+ end
12
+
13
+ # AuthenticationConfig model - gateway authentication configuration
14
+ class AuthenticationConfig < BaseModel
15
+ SWAGGER_TYPES = {
16
+ id: 'String',
17
+ application_id: 'String',
18
+ authentication_type: 'String',
19
+ api_keys: 'Array<String>',
20
+ audience: 'String',
21
+ conductor_token: 'String',
22
+ created_by: 'String',
23
+ fallback_to_default_auth: 'Boolean',
24
+ issuer_uri: 'String',
25
+ passthrough: 'Boolean',
26
+ token_in_workflow_input: 'Boolean',
27
+ updated_by: 'String'
28
+ }.freeze
29
+
30
+ ATTRIBUTE_MAP = {
31
+ id: :id,
32
+ application_id: :applicationId,
33
+ authentication_type: :authenticationType,
34
+ api_keys: :apiKeys,
35
+ audience: :audience,
36
+ conductor_token: :conductorToken,
37
+ created_by: :createdBy,
38
+ fallback_to_default_auth: :fallbackToDefaultAuth,
39
+ issuer_uri: :issuerUri,
40
+ passthrough: :passthrough,
41
+ token_in_workflow_input: :tokenInWorkflowInput,
42
+ updated_by: :updatedBy
43
+ }.freeze
44
+
45
+ attr_accessor :id, :application_id, :authentication_type, :api_keys,
46
+ :audience, :conductor_token, :created_by,
47
+ :fallback_to_default_auth, :issuer_uri, :passthrough,
48
+ :token_in_workflow_input, :updated_by
49
+
50
+ def initialize(params = {})
51
+ @id = params[:id]
52
+ @application_id = params[:application_id]
53
+ @authentication_type = params[:authentication_type]
54
+ @api_keys = params[:api_keys]
55
+ @audience = params[:audience]
56
+ @conductor_token = params[:conductor_token]
57
+ @created_by = params[:created_by]
58
+ @fallback_to_default_auth = params[:fallback_to_default_auth]
59
+ @issuer_uri = params[:issuer_uri]
60
+ @passthrough = params[:passthrough]
61
+ @token_in_workflow_input = params[:token_in_workflow_input]
62
+ @updated_by = params[:updated_by]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conductor
4
+ module Http
5
+ module Models
6
+ # Access type constants for authorization
7
+ module AccessType
8
+ CREATE = 'CREATE'
9
+ READ = 'READ'
10
+ UPDATE = 'UPDATE'
11
+ DELETE = 'DELETE'
12
+ EXECUTE = 'EXECUTE'
13
+ end
14
+
15
+ # AuthorizationRequest model - request to grant or revoke permissions
16
+ class AuthorizationRequest < BaseModel
17
+ SWAGGER_TYPES = {
18
+ subject: 'SubjectRef',
19
+ target: 'TargetRef',
20
+ access: 'Array<String>'
21
+ }.freeze
22
+
23
+ ATTRIBUTE_MAP = {
24
+ subject: :subject,
25
+ target: :target,
26
+ access: :access
27
+ }.freeze
28
+
29
+ attr_accessor :subject, :target, :access
30
+
31
+ def initialize(params = {})
32
+ @subject = params[:subject]
33
+ @target = params[:target]
34
+ @access = params[:access]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'time'
5
+
6
+ module Conductor
7
+ module Http
8
+ module Models
9
+ # Base class for all Conductor model objects
10
+ # Implements the SWAGGER_TYPES pattern from Python SDK
11
+ class BaseModel
12
+ # Class method to define swagger types and attribute mappings
13
+ def self.swagger_types
14
+ self::SWAGGER_TYPES
15
+ end
16
+
17
+ def self.attribute_map
18
+ self::ATTRIBUTE_MAP
19
+ end
20
+
21
+ # Convert model to hash using ATTRIBUTE_MAP for JSON keys
22
+ def to_h
23
+ hash = {}
24
+ self.class.attribute_map.each do |attr, json_key|
25
+ value = send(attr)
26
+ next if value.nil?
27
+
28
+ hash[json_key.to_s] = serialize_value(value)
29
+ end
30
+ hash
31
+ end
32
+
33
+ # Alias for to_h
34
+ alias to_hash to_h
35
+
36
+ # Convert model to JSON string
37
+ def to_json(*_args)
38
+ JSON.generate(to_h)
39
+ end
40
+
41
+ # Build model from hash (deserialization)
42
+ def self.from_hash(hash)
43
+ return nil unless hash
44
+ # If it's not a Hash (e.g., a string expression like "${workflow.input.foo}"),
45
+ # return it as-is rather than trying to deserialize
46
+ return hash unless hash.is_a?(Hash)
47
+
48
+ instance = new
49
+ attribute_map.each do |attr, json_key|
50
+ json_key_str = json_key.to_s
51
+ next unless hash.key?(json_key_str) || hash.key?(json_key.to_sym)
52
+
53
+ value = hash[json_key_str] || hash[json_key.to_sym]
54
+ type = swagger_types[attr]
55
+
56
+ instance.send("#{attr}=", deserialize_value(value, type))
57
+ end
58
+ instance
59
+ end
60
+
61
+ # Build model from JSON string
62
+ def self.from_json(json_string)
63
+ from_hash(JSON.parse(json_string))
64
+ end
65
+
66
+ private
67
+
68
+ # Serialize a value for JSON output
69
+ def serialize_value(value)
70
+ case value
71
+ when BaseModel
72
+ value.to_h
73
+ when Array
74
+ value.map { |v| serialize_value(v) }
75
+ when Hash
76
+ value.transform_values { |v| serialize_value(v) }
77
+ when Time, DateTime, Date
78
+ value.iso8601
79
+ else
80
+ value
81
+ end
82
+ end
83
+
84
+ # Deserialize a value based on type string
85
+ def self.deserialize_value(value, type)
86
+ return nil if value.nil?
87
+ return value if type.nil?
88
+
89
+ # Handle array types: "Array<Type>"
90
+ if type.start_with?('Array<')
91
+ inner_type = type[6..-2] # Extract Type from Array<Type>
92
+ return value.map { |v| deserialize_value(v, inner_type) }
93
+ end
94
+
95
+ # Handle hash types: "Hash<String, Type>" or "Hash{String => Type}"
96
+ if type.start_with?('Hash<', 'Hash{')
97
+ # Extract value type from Hash<K, V> or Hash{K => V}
98
+ match = type.match(/Hash[<{].*,\s*(.+)[>}]/)
99
+ value_type = match[1] if match
100
+ return value.transform_values { |v| deserialize_value(v, value_type) }
101
+ end
102
+
103
+ # Handle primitive types
104
+ case type
105
+ when 'String'
106
+ value.to_s
107
+ when 'Integer'
108
+ value.to_i
109
+ when 'Float'
110
+ value.to_f
111
+ when 'Boolean', 'BOOLEAN'
112
+ value.to_s.downcase == 'true'
113
+ when 'DateTime'
114
+ parse_datetime(value)
115
+ when 'Date'
116
+ Date.parse(value.to_s)
117
+ when 'Time'
118
+ Time.parse(value.to_s)
119
+ when 'Object'
120
+ value
121
+ else
122
+ # Model class - convert to proper class
123
+ deserialize_model(value, type)
124
+ end
125
+ end
126
+
127
+ def self.parse_datetime(value)
128
+ return value if value.is_a?(DateTime) || value.is_a?(Time)
129
+
130
+ # Try ISO8601 first
131
+ DateTime.iso8601(value.to_s)
132
+ rescue ArgumentError
133
+ # Fall back to regular parsing
134
+ DateTime.parse(value.to_s)
135
+ end
136
+
137
+ def self.deserialize_model(value, type)
138
+ # If value is not a Hash (e.g., a string expression), return as-is
139
+ return value unless value.is_a?(Hash)
140
+
141
+ # Try to find the model class
142
+ klass = find_model_class(type)
143
+ return value unless klass
144
+
145
+ # If it's already the right type, return it
146
+ return value if value.is_a?(klass)
147
+
148
+ # Deserialize hash to model
149
+ klass.respond_to?(:from_hash) ? klass.from_hash(value) : value
150
+ end
151
+
152
+ def self.find_model_class(type)
153
+ # Try to find class in Conductor::Http::Models namespace
154
+ const_name = type.split('::').last
155
+ Conductor::Http::Models.const_get(const_name) if Conductor::Http::Models.const_defined?(const_name)
156
+ rescue NameError
157
+ nil
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end