temporalio 0.2.0-x86_64-linux

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 (128) hide show
  1. checksums.yaml +7 -0
  2. data/lib/temporalio/activity/complete_async_error.rb +11 -0
  3. data/lib/temporalio/activity/context.rb +107 -0
  4. data/lib/temporalio/activity/definition.rb +77 -0
  5. data/lib/temporalio/activity/info.rb +63 -0
  6. data/lib/temporalio/activity.rb +69 -0
  7. data/lib/temporalio/api/batch/v1/message.rb +31 -0
  8. data/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb +93 -0
  9. data/lib/temporalio/api/cloud/cloudservice/v1/service.rb +25 -0
  10. data/lib/temporalio/api/cloud/cloudservice.rb +3 -0
  11. data/lib/temporalio/api/cloud/identity/v1/message.rb +36 -0
  12. data/lib/temporalio/api/cloud/namespace/v1/message.rb +35 -0
  13. data/lib/temporalio/api/cloud/operation/v1/message.rb +27 -0
  14. data/lib/temporalio/api/cloud/region/v1/message.rb +23 -0
  15. data/lib/temporalio/api/command/v1/message.rb +46 -0
  16. data/lib/temporalio/api/common/v1/grpc_status.rb +23 -0
  17. data/lib/temporalio/api/common/v1/message.rb +41 -0
  18. data/lib/temporalio/api/enums/v1/batch_operation.rb +22 -0
  19. data/lib/temporalio/api/enums/v1/command_type.rb +21 -0
  20. data/lib/temporalio/api/enums/v1/common.rb +26 -0
  21. data/lib/temporalio/api/enums/v1/event_type.rb +21 -0
  22. data/lib/temporalio/api/enums/v1/failed_cause.rb +26 -0
  23. data/lib/temporalio/api/enums/v1/namespace.rb +23 -0
  24. data/lib/temporalio/api/enums/v1/query.rb +22 -0
  25. data/lib/temporalio/api/enums/v1/reset.rb +23 -0
  26. data/lib/temporalio/api/enums/v1/schedule.rb +21 -0
  27. data/lib/temporalio/api/enums/v1/task_queue.rb +25 -0
  28. data/lib/temporalio/api/enums/v1/update.rb +22 -0
  29. data/lib/temporalio/api/enums/v1/workflow.rb +30 -0
  30. data/lib/temporalio/api/errordetails/v1/message.rb +42 -0
  31. data/lib/temporalio/api/export/v1/message.rb +24 -0
  32. data/lib/temporalio/api/failure/v1/message.rb +35 -0
  33. data/lib/temporalio/api/filter/v1/message.rb +27 -0
  34. data/lib/temporalio/api/history/v1/message.rb +90 -0
  35. data/lib/temporalio/api/namespace/v1/message.rb +31 -0
  36. data/lib/temporalio/api/nexus/v1/message.rb +40 -0
  37. data/lib/temporalio/api/operatorservice/v1/request_response.rb +49 -0
  38. data/lib/temporalio/api/operatorservice/v1/service.rb +23 -0
  39. data/lib/temporalio/api/operatorservice.rb +3 -0
  40. data/lib/temporalio/api/protocol/v1/message.rb +23 -0
  41. data/lib/temporalio/api/query/v1/message.rb +27 -0
  42. data/lib/temporalio/api/replication/v1/message.rb +26 -0
  43. data/lib/temporalio/api/schedule/v1/message.rb +42 -0
  44. data/lib/temporalio/api/sdk/v1/enhanced_stack_trace.rb +25 -0
  45. data/lib/temporalio/api/sdk/v1/task_complete_metadata.rb +21 -0
  46. data/lib/temporalio/api/sdk/v1/user_metadata.rb +23 -0
  47. data/lib/temporalio/api/sdk/v1/workflow_metadata.rb +23 -0
  48. data/lib/temporalio/api/taskqueue/v1/message.rb +45 -0
  49. data/lib/temporalio/api/update/v1/message.rb +33 -0
  50. data/lib/temporalio/api/version/v1/message.rb +26 -0
  51. data/lib/temporalio/api/workflow/v1/message.rb +43 -0
  52. data/lib/temporalio/api/workflowservice/v1/request_response.rb +189 -0
  53. data/lib/temporalio/api/workflowservice/v1/service.rb +23 -0
  54. data/lib/temporalio/api/workflowservice.rb +3 -0
  55. data/lib/temporalio/api.rb +13 -0
  56. data/lib/temporalio/cancellation.rb +150 -0
  57. data/lib/temporalio/client/activity_id_reference.rb +32 -0
  58. data/lib/temporalio/client/async_activity_handle.rb +110 -0
  59. data/lib/temporalio/client/connection/cloud_service.rb +648 -0
  60. data/lib/temporalio/client/connection/operator_service.rb +249 -0
  61. data/lib/temporalio/client/connection/service.rb +41 -0
  62. data/lib/temporalio/client/connection/workflow_service.rb +1218 -0
  63. data/lib/temporalio/client/connection.rb +270 -0
  64. data/lib/temporalio/client/interceptor.rb +316 -0
  65. data/lib/temporalio/client/workflow_execution.rb +103 -0
  66. data/lib/temporalio/client/workflow_execution_count.rb +36 -0
  67. data/lib/temporalio/client/workflow_execution_status.rb +18 -0
  68. data/lib/temporalio/client/workflow_handle.rb +446 -0
  69. data/lib/temporalio/client/workflow_query_reject_condition.rb +14 -0
  70. data/lib/temporalio/client/workflow_update_handle.rb +67 -0
  71. data/lib/temporalio/client/workflow_update_wait_stage.rb +17 -0
  72. data/lib/temporalio/client.rb +404 -0
  73. data/lib/temporalio/common_enums.rb +24 -0
  74. data/lib/temporalio/converters/data_converter.rb +102 -0
  75. data/lib/temporalio/converters/failure_converter.rb +200 -0
  76. data/lib/temporalio/converters/payload_codec.rb +26 -0
  77. data/lib/temporalio/converters/payload_converter/binary_null.rb +34 -0
  78. data/lib/temporalio/converters/payload_converter/binary_plain.rb +35 -0
  79. data/lib/temporalio/converters/payload_converter/binary_protobuf.rb +42 -0
  80. data/lib/temporalio/converters/payload_converter/composite.rb +62 -0
  81. data/lib/temporalio/converters/payload_converter/encoding.rb +35 -0
  82. data/lib/temporalio/converters/payload_converter/json_plain.rb +44 -0
  83. data/lib/temporalio/converters/payload_converter/json_protobuf.rb +41 -0
  84. data/lib/temporalio/converters/payload_converter.rb +73 -0
  85. data/lib/temporalio/converters.rb +9 -0
  86. data/lib/temporalio/error/failure.rb +219 -0
  87. data/lib/temporalio/error.rb +147 -0
  88. data/lib/temporalio/internal/bridge/3.1/temporalio_bridge.so +0 -0
  89. data/lib/temporalio/internal/bridge/3.2/temporalio_bridge.so +0 -0
  90. data/lib/temporalio/internal/bridge/3.3/temporalio_bridge.so +0 -0
  91. data/lib/temporalio/internal/bridge/api/activity_result/activity_result.rb +34 -0
  92. data/lib/temporalio/internal/bridge/api/activity_task/activity_task.rb +31 -0
  93. data/lib/temporalio/internal/bridge/api/child_workflow/child_workflow.rb +33 -0
  94. data/lib/temporalio/internal/bridge/api/common/common.rb +26 -0
  95. data/lib/temporalio/internal/bridge/api/core_interface.rb +36 -0
  96. data/lib/temporalio/internal/bridge/api/external_data/external_data.rb +27 -0
  97. data/lib/temporalio/internal/bridge/api/workflow_activation/workflow_activation.rb +52 -0
  98. data/lib/temporalio/internal/bridge/api/workflow_commands/workflow_commands.rb +54 -0
  99. data/lib/temporalio/internal/bridge/api/workflow_completion/workflow_completion.rb +30 -0
  100. data/lib/temporalio/internal/bridge/api.rb +3 -0
  101. data/lib/temporalio/internal/bridge/client.rb +90 -0
  102. data/lib/temporalio/internal/bridge/runtime.rb +53 -0
  103. data/lib/temporalio/internal/bridge/testing.rb +46 -0
  104. data/lib/temporalio/internal/bridge/worker.rb +83 -0
  105. data/lib/temporalio/internal/bridge.rb +36 -0
  106. data/lib/temporalio/internal/client/implementation.rb +525 -0
  107. data/lib/temporalio/internal/proto_utils.rb +54 -0
  108. data/lib/temporalio/internal/worker/activity_worker.rb +345 -0
  109. data/lib/temporalio/internal/worker/multi_runner.rb +169 -0
  110. data/lib/temporalio/internal.rb +7 -0
  111. data/lib/temporalio/retry_policy.rb +51 -0
  112. data/lib/temporalio/runtime.rb +271 -0
  113. data/lib/temporalio/scoped_logger.rb +96 -0
  114. data/lib/temporalio/search_attributes.rb +300 -0
  115. data/lib/temporalio/testing/activity_environment.rb +132 -0
  116. data/lib/temporalio/testing/workflow_environment.rb +137 -0
  117. data/lib/temporalio/testing.rb +10 -0
  118. data/lib/temporalio/version.rb +5 -0
  119. data/lib/temporalio/worker/activity_executor/fiber.rb +49 -0
  120. data/lib/temporalio/worker/activity_executor/thread_pool.rb +254 -0
  121. data/lib/temporalio/worker/activity_executor.rb +55 -0
  122. data/lib/temporalio/worker/interceptor.rb +88 -0
  123. data/lib/temporalio/worker/tuner.rb +151 -0
  124. data/lib/temporalio/worker.rb +426 -0
  125. data/lib/temporalio/workflow_history.rb +22 -0
  126. data/lib/temporalio.rb +7 -0
  127. data/temporalio.gemspec +28 -0
  128. metadata +189 -0
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/internal/bridge/runtime'
4
+
5
+ module Temporalio
6
+ # Runtime for Temporal Ruby SDK.
7
+ #
8
+ # Only one global {Runtime} needs to exist. Users are encouraged to use {default}. To configure it, create a runtime
9
+ # before any clients are created, and set it via {default=}. Every time a new runtime is created, a new internal Rust
10
+ # thread pool is created.
11
+ class Runtime
12
+ # Telemetry options for the runtime.
13
+ #
14
+ # @!attribute logging
15
+ # @return [LoggingOptions, nil] Logging options, default is new {LoggingOptions} with no parameters. Can be set
16
+ # to nil to disable logging.
17
+ # @!attribute metrics
18
+ # @return [MetricsOptions, nil] Metrics options.
19
+ TelemetryOptions = Struct.new(
20
+ :logging,
21
+ :metrics,
22
+ keyword_init: true
23
+ ) do
24
+ # @!visibility private
25
+ def initialize(**kwargs)
26
+ # @type var kwargs: untyped
27
+ kwargs[:logging] = LoggingOptions.new unless kwargs.key?(:logging)
28
+ super
29
+ end
30
+
31
+ # @!visibility private
32
+ def _to_bridge
33
+ # @type self: TelemetryOptions
34
+ Internal::Bridge::Runtime::TelemetryOptions.new(
35
+ logging: logging&._to_bridge,
36
+ metrics: metrics&._to_bridge
37
+ )
38
+ end
39
+ end
40
+
41
+ # Logging options for runtime telemetry.
42
+ #
43
+ # @!attribute log_filter
44
+ # @return [LoggingFilterOptions, String] Logging filter for Core, default is new {LoggingFilterOptions} with no
45
+ # parameters.
46
+ LoggingOptions = Struct.new(
47
+ :log_filter,
48
+ # TODO(cretz): forward_to
49
+ keyword_init: true
50
+ ) do
51
+ # @!visibility private
52
+ def initialize(**kwargs)
53
+ # @type var kwargs: untyped
54
+ kwargs[:log_filter] = LoggingFilterOptions.new unless kwargs.key?(:log_filter)
55
+ super
56
+ end
57
+
58
+ # @!visibility private
59
+ def _to_bridge
60
+ # @type self: LoggingOptions
61
+ Internal::Bridge::Runtime::LoggingOptions.new(
62
+ log_filter: if log_filter.is_a?(String)
63
+ log_filter
64
+ elsif log_filter.is_a?(LoggingFilterOptions)
65
+ log_filter._to_bridge
66
+ else
67
+ raise 'Log filter must be string or LoggingFilterOptions'
68
+ end
69
+ )
70
+ end
71
+ end
72
+
73
+ # Logging filter options for Core.
74
+ #
75
+ # @!attribute core_level
76
+ # @return ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for Core log messages, default is +'WARN'+.
77
+ # @!attribute other_level
78
+ # @return ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] Log level for other Rust log messages, default is +'WARN'+.
79
+ LoggingFilterOptions = Struct.new(
80
+ :core_level,
81
+ :other_level,
82
+ keyword_init: true
83
+ ) do
84
+ # @!visibility private
85
+ def initialize(**kwargs)
86
+ # @type var kwargs: untyped
87
+ kwargs[:core_level] = 'WARN' unless kwargs.key?(:core_level)
88
+ kwargs[:other_level] = 'ERROR' unless kwargs.key?(:other_level)
89
+ super
90
+ end
91
+
92
+ # @!visibility private
93
+ def _to_bridge
94
+ # @type self: LoggingFilterOptions
95
+ "#{other_level},temporal_sdk_core=#{core_level},temporal_client=#{core_level},temporal_sdk=#{core_level}"
96
+ end
97
+ end
98
+
99
+ # Metrics options for runtime telemetry. Either {opentelemetry} or {prometheus} required, but not both.
100
+ #
101
+ # @!attribute opentelemetry
102
+ # @return [OpenTelemetryMetricsOptions, nil] OpenTelemetry options if using OpenTelemetry. This is mutually
103
+ # exclusive with +prometheus+.
104
+ # @!attribute prometheus
105
+ # @return [PrometheusMetricsOptions, nil] Prometheus options if using Prometheus. This is mutually exclusive with
106
+ # +opentelemetry+.
107
+ # @!attribute attach_service_name
108
+ # @return [Boolean] Whether to put the service_name on every metric, default +true+.
109
+ # @!attribute global_tags
110
+ # @return [Hash<String, String>, nil] Resource tags to be applied to all metrics.
111
+ # @!attribute metric_prefix
112
+ # @return [String, nil] Prefix to put on every Temporal metric. If unset, defaults to +temporal_+.
113
+ MetricsOptions = Struct.new(
114
+ :opentelemetry,
115
+ :prometheus,
116
+ :attach_service_name,
117
+ :global_tags,
118
+ :metric_prefix,
119
+ keyword_init: true
120
+ ) do
121
+ # @!visibility private
122
+ def initialize(**kwargs)
123
+ # @type var kwargs: untyped
124
+ kwargs[:attach_service_name] = true unless kwargs.key?(:attach_service_name)
125
+ super
126
+ end
127
+
128
+ # @!visibility private
129
+ def _to_bridge
130
+ # @type self: MetricsOptions
131
+ Internal::Bridge::Runtime::MetricsOptions.new(
132
+ opentelemetry: opentelemetry&._to_bridge,
133
+ prometheus: prometheus&._to_bridge,
134
+ attach_service_name:,
135
+ global_tags:,
136
+ metric_prefix:
137
+ )
138
+ end
139
+ end
140
+
141
+ # Options for exporting metrics to OpenTelemetry.
142
+ #
143
+ # @!attribute url
144
+ # @return [String] URL for OpenTelemetry endpoint.
145
+ # @!attribute headers
146
+ # @return [Hash<String, String>, nil] Headers for OpenTelemetry endpoint.
147
+ # @!attribute metric_periodicity
148
+ # @return [Float, nil] How frequently metrics should be exported, unset uses internal default.
149
+ # @!attribute metric_temporality
150
+ # @return [MetricTemporality] How frequently metrics should be exported, default is
151
+ # {MetricTemporality::CUMULATIVE}.
152
+ # @!attribute durations_as_seconds
153
+ # @return [Boolean] Whether to use float seconds instead of integer milliseconds for durations, default is
154
+ # +false+.
155
+ OpenTelemetryMetricsOptions = Struct.new(
156
+ :url,
157
+ :headers,
158
+ :metric_periodicity,
159
+ :metric_temporality,
160
+ :durations_as_seconds,
161
+ keyword_init: true
162
+ ) do
163
+ # OpenTelemetry metric temporality.
164
+ module MetricTemporality # rubocop:disable Lint/ConstantDefinitionInBlock
165
+ CUMULATIVE = 1
166
+ DELTA = 2
167
+ end
168
+
169
+ # @!visibility private
170
+ def initialize(**kwargs)
171
+ # @type var kwargs: untyped
172
+ kwargs[:metric_temporality] = MetricTemporality::CUMULATIVE unless kwargs.key?(:metric_temporality)
173
+ kwargs[:durations_as_seconds] = false unless kwargs.key?(:durations_as_seconds)
174
+ super
175
+ end
176
+
177
+ # @!visibility private
178
+ def _to_bridge
179
+ # @type self: OpenTelemetryMetricsOptions
180
+ Internal::Bridge::Runtime::OpenTelemetryMetricsOptions.new(
181
+ url:,
182
+ headers:,
183
+ metric_periodicity:,
184
+ metric_temporality_delta: case metric_temporality
185
+ when MetricTemporality::CUMULATIVE then false
186
+ when MetricTemporality::DELTA then true
187
+ else raise 'Unrecognized metric temporality'
188
+ end,
189
+ durations_as_seconds:
190
+ )
191
+ end
192
+ end
193
+
194
+ # Options for exporting metrics to Prometheus.
195
+ #
196
+ # @!attribute bind_address
197
+ # @return [String] Address to bind to for Prometheus endpoint.
198
+ # @!attribute counters_total_suffix
199
+ # @return [Boolean] If +true+, all counters will include a +_total+ suffix, default is +false+.
200
+ # @!attribute unit_suffix
201
+ # @return [Boolean] If +true+, all histograms will include the unit in their name as a suffix, default is +false+.
202
+ # @!attribute durations_as_seconds
203
+ # @return [Boolean] Whether to use float seconds instead of integer milliseconds for durations, default is
204
+ # +false+.
205
+ PrometheusMetricsOptions = Struct.new(
206
+ :bind_address,
207
+ :counters_total_suffix,
208
+ :unit_suffix,
209
+ :durations_as_seconds,
210
+ keyword_init: true
211
+ ) do
212
+ # @!visibility private
213
+ def initialize(**kwargs)
214
+ # @type var kwargs: untyped
215
+ kwargs[:counters_total_suffix] = false unless kwargs.key?(:counters_total_suffix)
216
+ kwargs[:unit_suffix] = false unless kwargs.key?(:unit_suffix)
217
+ kwargs[:durations_as_seconds] = false unless kwargs.key?(:durations_as_seconds)
218
+ super
219
+ end
220
+
221
+ # @!visibility private
222
+ def _to_bridge
223
+ # @type self: PrometheusMetricsOptions
224
+ Internal::Bridge::Runtime::PrometheusMetricsOptions.new(
225
+ bind_address:,
226
+ counters_total_suffix:,
227
+ unit_suffix:,
228
+ durations_as_seconds:
229
+ )
230
+ end
231
+ end
232
+
233
+ # Default runtime, lazily created upon first access. If needing a different default, make sure it is updated via
234
+ # {default=} before this is called (either directly or as a parameter to something like {Client}).
235
+ #
236
+ # @return [Runtime] Default runtime.
237
+ def self.default
238
+ @default ||= Runtime.new
239
+ end
240
+
241
+ # Set the default runtime. Must be called before {default} accessed.
242
+ #
243
+ # @param runtime [Runtime] Runtime to set as default.
244
+ # @raise If default has already been accessed.
245
+ def self.default=(runtime)
246
+ raise 'Runtime already set or requested' unless @default.nil?
247
+
248
+ @default = runtime
249
+ end
250
+
251
+ # Create new Runtime. For most users, this should only be done once globally. In addition to creating a Rust thread
252
+ # pool, this also consumes a Ruby thread for its lifetime.
253
+ #
254
+ # @param telemetry [TelemetryOptions] Telemetry options to set.
255
+ def initialize(telemetry: TelemetryOptions.new)
256
+ @core_runtime = Internal::Bridge::Runtime.new(
257
+ Internal::Bridge::Runtime::Options.new(telemetry: telemetry._to_bridge)
258
+ )
259
+ # We need a thread to run the command loop
260
+ # TODO(cretz): Is this something users should be concerned about or need control over?
261
+ Thread.new do
262
+ @core_runtime.run_command_loop
263
+ end
264
+ end
265
+
266
+ # @!visibility private
267
+ def _core_runtime
268
+ @core_runtime
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+ require 'logger'
5
+
6
+ module Temporalio
7
+ # Implementation via delegator to {::Logger} that puts scoped values on the log message and appends them to the log
8
+ # message.
9
+ class ScopedLogger < SimpleDelegator
10
+ # @!attribute scoped_values_getter
11
+ # @return [Proc, nil] Proc to call to get scoped values when needed.
12
+ attr_accessor :scoped_values_getter
13
+
14
+ # @!attribute scoped_values_getter
15
+ # @return [Boolean] Whether the scoped value appending is disabled.
16
+ attr_accessor :disable_scoped_values
17
+
18
+ # @see Logger.add
19
+ def add(severity, message = nil, progname = nil)
20
+ return true if (severity || Logger::Unknown) < level
21
+ return super if scoped_values_getter.nil? || @disable_scoped_values
22
+
23
+ scoped_values = scoped_values_getter.call
24
+ return super if scoped_values.nil?
25
+
26
+ if message.nil?
27
+ if block_given?
28
+ message = yield
29
+ else
30
+ message = progname
31
+ progname = nil
32
+ end
33
+ end
34
+ # For exceptions we need to dup and append here, for everything else we
35
+ # need to delegate to a log message
36
+ new_message = if message.is_a?(Exception)
37
+ message.exception("#{message.message} #{scoped_values}")
38
+ else
39
+ LogMessage.new(message, scoped_values)
40
+ end
41
+ super(severity, new_message, progname)
42
+ end
43
+ alias log add
44
+
45
+ # @see Logger.debug
46
+ def debug(progname = nil, &)
47
+ add(Logger::DEBUG, nil, progname, &)
48
+ end
49
+
50
+ # @see Logger.info
51
+ def info(progname = nil, &)
52
+ add(Logger::INFO, nil, progname, &)
53
+ end
54
+
55
+ # @see Logger.warn
56
+ def warn(progname = nil, &)
57
+ add(Logger::WARN, nil, progname, &)
58
+ end
59
+
60
+ # @see Logger.error
61
+ def error(progname = nil, &)
62
+ add(Logger::ERROR, nil, progname, &)
63
+ end
64
+
65
+ # @see Logger.fatal
66
+ def fatal(progname = nil, &)
67
+ add(Logger::FATAL, nil, progname, &)
68
+ end
69
+
70
+ # @see Logger.unknown
71
+ def unknown(progname = nil, &)
72
+ add(Logger::UNKNOWN, nil, progname, &)
73
+ end
74
+
75
+ # Scoped log message wrapping original log message.
76
+ class LogMessage
77
+ # @return [Object] Original log message.
78
+ attr_reader :message
79
+
80
+ # @return [Object] Scoped values.
81
+ attr_reader :scoped_values
82
+
83
+ # @!visibility private
84
+ def initialize(message, scoped_values)
85
+ @message = message
86
+ @scoped_values = scoped_values
87
+ end
88
+
89
+ # @return [String] Message with scoped values appended.
90
+ def inspect
91
+ message_str = message.is_a?(String) ? message : message.inspect
92
+ "#{message_str} #{scoped_values}"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temporalio/api'
4
+
5
+ module Temporalio
6
+ # Collection of typed search attributes.
7
+ #
8
+ # This is represented as a mapping of {SearchAttributes::Key} to object values. This is not a hash though it does have
9
+ # a few hash-like methods and can be converted to a hash via {#to_h}. In some situations, such as in workflows, this
10
+ # class is frozen.
11
+ class SearchAttributes
12
+ # Key for a search attribute.
13
+ class Key
14
+ # @return [String] Name of the search attribute.
15
+ attr_reader :name
16
+
17
+ # @return [IndexedValueType] Type of the search attribute.
18
+ attr_reader :type
19
+
20
+ def initialize(name, type)
21
+ raise ArgumentError, 'Invalid type' unless Api::Enums::V1::IndexedValueType.lookup(type)
22
+
23
+ @name = name
24
+ @type = type
25
+ end
26
+
27
+ # @return [Boolean] Check equality.
28
+ def ==(other)
29
+ other.is_a?(Key) && other.name == @name && other.type == @type
30
+ end
31
+
32
+ alias eql? ==
33
+
34
+ # @return [Integer] Hash
35
+ def hash
36
+ [self.class, @name, @age].hash
37
+ end
38
+
39
+ # Validate that the given value matches the expected {#type}.
40
+ #
41
+ # @raise [TypeError] The value does not have the proper type.
42
+ def validate_value(value)
43
+ case type
44
+ when IndexedValueType::TEXT
45
+ raise TypeError, 'Value of TEXT key must be a String' unless value.is_a?(String)
46
+ when IndexedValueType::KEYWORD
47
+ raise TypeError, 'Value of KEYWORD key must be a String' unless value.is_a?(String)
48
+ when IndexedValueType::INTEGER
49
+ raise TypeError, 'Value of INTEGER key must be a Integer' unless value.is_a?(Integer)
50
+ when IndexedValueType::FLOAT
51
+ unless value.is_a?(Float) || value.is_a?(Integer)
52
+ raise TypeError, 'Value of FLOAT key must be a Float or Integer'
53
+ end
54
+ when IndexedValueType::BOOLEAN
55
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
56
+ raise TypeError, 'Value of BOOLEAN key must be a Boolean'
57
+ end
58
+ when IndexedValueType::TIME
59
+ raise TypeError, 'Value of TIME key must be a Time' unless value.is_a?(Time)
60
+ when IndexedValueType::KEYWORD_LIST
61
+ unless value.is_a?(Array) && value.all? { |v| v.is_a?(String) }
62
+ raise TypeError, 'Value of KEYWORD_LIST key must be an Array of String'
63
+ end
64
+ else
65
+ # Should never happen, checked in constructor
66
+ raise 'Unrecognized key type'
67
+ end
68
+ end
69
+
70
+ # Create an updated that sets the given value for this key.
71
+ #
72
+ # @param value [Object] Value to update. Must be the proper type for the key.
73
+ # @return [Update] Created update.
74
+ def value_set(value)
75
+ raise ArgumentError, 'Value cannot be nil, use value_unset' if value.nil?
76
+
77
+ Update.new(self, value)
78
+ end
79
+
80
+ # Create an updated that unsets the key.
81
+ #
82
+ # @return [Update] Created update.
83
+ def value_unset
84
+ Update.new(self, nil)
85
+ end
86
+ end
87
+
88
+ # Search attribute update that can be separately applied.
89
+ class Update
90
+ # @return [Key] Key this update applies to.
91
+ attr_reader :key
92
+
93
+ # @return [Object, nil] Value to update or `nil` to remove the key.
94
+ attr_reader :value
95
+
96
+ # Create an update. Users may find it easier to use {Key#value_set} and {Key#value_unset} instead.
97
+ #
98
+ # @param key [Key] Key to update.
99
+ # @param value [Object, nil] Value to update to or nil to remove the value.
100
+ def initialize(key, value)
101
+ raise ArgumentError, 'Key must be a key' unless key.is_a?(Key)
102
+
103
+ key.validate_value(value) unless value.nil?
104
+ @key = key
105
+ @value = value
106
+ end
107
+ end
108
+
109
+ # @!visibility private
110
+ def self.from_proto(proto)
111
+ return nil unless proto
112
+ raise ArgumentError, 'Expected proto search attribute' unless proto.is_a?(Api::Common::V1::SearchAttributes)
113
+
114
+ SearchAttributes.new(proto.indexed_fields.map do |key_name, payload| # rubocop:disable Style/MapToHash
115
+ key = Key.new(key_name, IndexedValueType::PROTO_VALUES[payload.metadata['type']])
116
+ value = value_from_payload(payload)
117
+ [key, value]
118
+ end.to_h)
119
+ end
120
+
121
+ # @!visibility private
122
+ def self.value_from_payload(payload)
123
+ value = Converters::PayloadConverter.default.from_payload(payload)
124
+ # Time needs to be converted
125
+ value = Time.iso8601(value) if payload.metadata['type'] == 'DateTime' && value.is_a?(String)
126
+ value
127
+ end
128
+
129
+ # Create a search attribute collection.
130
+ #
131
+ # @param existing [SearchAttributes, Hash<Key, Object>, nil] Existing collection. This can be another
132
+ # {SearchAttributes} instance or a {::Hash}.
133
+ def initialize(existing = nil)
134
+ if existing.nil?
135
+ @raw_hash = {}
136
+ elsif existing.is_a?(SearchAttributes)
137
+ @raw_hash = existing.to_h
138
+ elsif existing.is_a?(Hash)
139
+ @raw_hash = {}
140
+ existing.each { |key, value| self[key] = value }
141
+ else
142
+ raise ArgumentError, 'Existing must be nil, a SearchAttributes instance, or a valid Hash'
143
+ end
144
+ end
145
+
146
+ # Set a search attribute value for a key. This will replace any existing value for the {Key#name }regardless of
147
+ # {Key#type}.
148
+ #
149
+ # @param key [Key] A key to set. This must be a {Key} and the value must be proper for the {Key#type}.
150
+ # @param value [Object, nil] The value to set. If `nil`, the key is removed. The value must be proper for the `key`.
151
+ def []=(key, value)
152
+ # Key must be a Key
153
+ raise ArgumentError, 'Key must be a key' unless key.is_a?(Key)
154
+
155
+ key.validate_value(value) unless value.nil?
156
+
157
+ # Remove any key with the same name and set
158
+ delete(key)
159
+ # We only set the value if it's non-nil, otherwise it's a delete
160
+ @raw_hash[key] = value unless value.nil?
161
+ end
162
+
163
+ # Get a search attribute value for a key.
164
+ #
165
+ # @param key [Key, String] The key to find. If this is a {Key}, it will use key equality (i.e. name and type) to
166
+ # search. If this is a {::String}, the type is not checked when finding the proper key.
167
+ # @return [Object, nil] Value if found or `nil` if not.
168
+ def [](key)
169
+ # Key must be a Key or a string
170
+ if key.is_a?(Key)
171
+ @raw_hash[key]
172
+ elsif key.is_a?(String)
173
+ @raw_hash.find { |hash_key, _| hash_key.name == key }&.last
174
+ else
175
+ raise ArgumentError, 'Key must be a key or string'
176
+ end
177
+ end
178
+
179
+ # Delete a search attribute key
180
+ #
181
+ # @param key [Key, String] The key to delete. Regardless of whether this is a {Key} or a {::String}, the key with
182
+ # the matching name will be deleted. This means a {Key} with a matching name but different type may be deleted.
183
+ def delete(key)
184
+ # Key must be a Key or a string, but we delete all values for the
185
+ # name no matter what
186
+ name = if key.is_a?(Key)
187
+ key.name
188
+ elsif key.is_a?(String)
189
+ key
190
+ else
191
+ raise ArgumentError, 'Key must be a key or string'
192
+ end
193
+ @raw_hash.delete_if { |hash_key, _| hash_key.name == name }
194
+ end
195
+
196
+ # Like {::Hash#each}.
197
+ def each(&)
198
+ @raw_hash.each(&)
199
+ end
200
+
201
+ # @return [Hash<Key, Object>] Copy of the search attributes as a hash.
202
+ def to_h
203
+ @raw_hash.dup
204
+ end
205
+
206
+ # @return [SearchAttributes] Copy of the search attributes.
207
+ def dup
208
+ SearchAttributes.new(self)
209
+ end
210
+
211
+ # @return [Boolean] Whether the set of attributes is empty.
212
+ def empty?
213
+ length.zero?
214
+ end
215
+
216
+ # @return [Integer] Number of attributes.
217
+ def length
218
+ @raw_hash.length
219
+ end
220
+
221
+ alias size length
222
+
223
+ # Return a new search attributes collection with updates applied.
224
+ #
225
+ # @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
226
+ # @return [SearchAttributes] New collection.
227
+ def update(*updates)
228
+ attrs = dup
229
+ attrs.update!(*updates)
230
+ attrs
231
+ end
232
+
233
+ # Update this search attribute collection with given updates.
234
+ #
235
+ # @param updates [Update] Updates created via {Key#value_set} or {Key#value_unset}.
236
+ def update!(*updates)
237
+ updates.each do |update|
238
+ raise ArgumentError, 'Update must be an update' unless update.is_a?(Update)
239
+
240
+ self[update.key] = update.value
241
+ end
242
+ end
243
+
244
+ # @!visibility private
245
+ def to_proto
246
+ Api::Common::V1::SearchAttributes.new(
247
+ indexed_fields: @raw_hash.to_h do |key, value|
248
+ # We use a default converter, but if type is a time, we need ISO format
249
+ value = value.iso8601 if key.type == IndexedValueType::TIME
250
+
251
+ # Convert to payload
252
+ payload = Converters::PayloadConverter.default.to_payload(value)
253
+ payload.metadata['type'] = IndexedValueType::PROTO_NAMES[key.type]
254
+
255
+ [key.name, payload]
256
+ end
257
+ )
258
+ end
259
+
260
+ # Type for a search attribute key/value.
261
+ #
262
+ # @see https://docs.temporal.io/visibility#supported-types
263
+ module IndexedValueType
264
+ # Text type, values must be {::String}.
265
+ TEXT = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_TEXT
266
+
267
+ # Keyword type, values must be {::String}.
268
+ KEYWORD = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_KEYWORD
269
+
270
+ # Integer type, values must be {::Integer}.
271
+ INTEGER = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_INT
272
+
273
+ # Float type, values must be {::Float} or {::Integer}.
274
+ FLOAT = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_DOUBLE
275
+
276
+ # Boolean type, values must be {::TrueClass} or {::FalseClass}.
277
+ BOOLEAN = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_BOOL
278
+
279
+ # Time type, values must be {::Time}.
280
+ TIME = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_DATETIME
281
+
282
+ # Keyword list type, values must be {::Array<String>}.
283
+ KEYWORD_LIST = Api::Enums::V1::IndexedValueType::INDEXED_VALUE_TYPE_KEYWORD_LIST
284
+
285
+ # @!visibility private
286
+ PROTO_NAMES = {
287
+ TEXT => 'Text',
288
+ KEYWORD => 'Keyword',
289
+ INTEGER => 'Int',
290
+ FLOAT => 'Double',
291
+ BOOLEAN => 'Bool',
292
+ TIME => 'DateTime',
293
+ KEYWORD_LIST => 'KeywordList'
294
+ }.freeze
295
+
296
+ # @!visibility private
297
+ PROTO_VALUES = PROTO_NAMES.invert.freeze
298
+ end
299
+ end
300
+ end