wingify-fme-ruby-sdk 1.50.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/lib/resources/debug_messages.json +21 -0
- data/lib/resources/error_messages.json +63 -0
- data/lib/resources/info_messages.json +43 -0
- data/lib/resources/warn_messages.json +1 -0
- data/lib/vwo.rb +40 -0
- data/lib/wingify/api/get_flag.rb +244 -0
- data/lib/wingify/api/set_attribute.rb +57 -0
- data/lib/wingify/api/track_event.rb +80 -0
- data/lib/wingify/constants/constants.rb +106 -0
- data/lib/wingify/decorators/storage_decorator.rb +82 -0
- data/lib/wingify/enums/api_enum.rb +23 -0
- data/lib/wingify/enums/campaign_type_enum.rb +19 -0
- data/lib/wingify/enums/debug_category_enum.rb +20 -0
- data/lib/wingify/enums/decision_types_enum.rb +18 -0
- data/lib/wingify/enums/event_enum.rb +22 -0
- data/lib/wingify/enums/headers_enum.rb +20 -0
- data/lib/wingify/enums/hooks_enum.rb +17 -0
- data/lib/wingify/enums/http_method_enum.rb +18 -0
- data/lib/wingify/enums/log_level_enum.rb +21 -0
- data/lib/wingify/enums/log_level_to_number.rb +27 -0
- data/lib/wingify/enums/status_enum.rb +19 -0
- data/lib/wingify/enums/storage_enum.rb +22 -0
- data/lib/wingify/enums/url_enum.rb +22 -0
- data/lib/wingify/models/campaign/campaign_model.rb +192 -0
- data/lib/wingify/models/campaign/feature_model.rb +111 -0
- data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
- data/lib/wingify/models/campaign/metric_model.rb +44 -0
- data/lib/wingify/models/campaign/rule_model.rb +56 -0
- data/lib/wingify/models/campaign/variable_model.rb +51 -0
- data/lib/wingify/models/campaign/variation_model.rb +137 -0
- data/lib/wingify/models/gateway_service_model.rb +39 -0
- data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
- data/lib/wingify/models/settings/settings_model.rb +101 -0
- data/lib/wingify/models/storage/storage_data_model.rb +44 -0
- data/lib/wingify/models/user/context_model.rb +154 -0
- data/lib/wingify/models/user/context_vwo_model.rb +38 -0
- data/lib/wingify/models/user/get_flag_response.rb +50 -0
- data/lib/wingify/models/vwo_options_model.rb +129 -0
- data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
- data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
- data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
- data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
- data/lib/wingify/packages/logger/logger.rb +38 -0
- data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
- data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
- data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
- data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
- data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
- data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
- data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
- data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
- data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
- data/lib/wingify/packages/storage/connector.rb +26 -0
- data/lib/wingify/packages/storage/storage.rb +47 -0
- data/lib/wingify/services/batch_event_queue.rb +179 -0
- data/lib/wingify/services/campaign_decision_service.rb +161 -0
- data/lib/wingify/services/hooks_service.rb +51 -0
- data/lib/wingify/services/logger_service.rb +114 -0
- data/lib/wingify/services/settings_service.rb +178 -0
- data/lib/wingify/services/storage_service.rb +66 -0
- data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
- data/lib/wingify/utils/brand_context.rb +33 -0
- data/lib/wingify/utils/brand_util.rb +53 -0
- data/lib/wingify/utils/campaign_util.rb +284 -0
- data/lib/wingify/utils/data_type_util.rb +105 -0
- data/lib/wingify/utils/debugger_service_util.rb +40 -0
- data/lib/wingify/utils/decision_util.rb +259 -0
- data/lib/wingify/utils/event_util.rb +55 -0
- data/lib/wingify/utils/function_util.rb +141 -0
- data/lib/wingify/utils/gateway_service_util.rb +101 -0
- data/lib/wingify/utils/impression_util.rb +66 -0
- data/lib/wingify/utils/log_message_util.rb +42 -0
- data/lib/wingify/utils/meg_util.rb +357 -0
- data/lib/wingify/utils/network_util.rb +503 -0
- data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
- data/lib/wingify/utils/settings_util.rb +38 -0
- data/lib/wingify/utils/usage_stats_util.rb +119 -0
- data/lib/wingify/utils/uuid_util.rb +96 -0
- data/lib/wingify/wingify_builder.rb +261 -0
- data/lib/wingify/wingify_client.rb +227 -0
- data/lib/wingify.rb +117 -0
- metadata +327 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require 'json'
|
|
16
|
+
require_relative '../../../decorators/storage_decorator'
|
|
17
|
+
require_relative '../../../models/settings/settings_model'
|
|
18
|
+
require_relative '../../../models/user/context_model'
|
|
19
|
+
require_relative '../../../models/campaign/feature_model'
|
|
20
|
+
require_relative '../../../services/storage_service'
|
|
21
|
+
require_relative '../../../services/logger_service'
|
|
22
|
+
require_relative '../enums/segment_operator_value_enum'
|
|
23
|
+
require_relative '../core/segmentation_manager'
|
|
24
|
+
require_relative '../utils/segment_util'
|
|
25
|
+
require_relative './segment_operand_evaluator'
|
|
26
|
+
require_relative '../../../enums/log_level_enum'
|
|
27
|
+
require_relative '../../../enums/api_enum'
|
|
28
|
+
|
|
29
|
+
class SegmentEvaluator
|
|
30
|
+
attr_accessor :context, :settings, :feature
|
|
31
|
+
|
|
32
|
+
def initialize(context = nil, settings = nil, feature = nil)
|
|
33
|
+
@context = context
|
|
34
|
+
@settings = settings
|
|
35
|
+
@feature = feature
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Validates if the segmentation defined in the DSL is applicable based on the provided properties.
|
|
39
|
+
# @param dsl [Hash] The DSL node to evaluate
|
|
40
|
+
# @param properties [Hash] The properties to evaluate the DSL against
|
|
41
|
+
# @return [Boolean] True if the segmentation is valid, false otherwise
|
|
42
|
+
def is_segmentation_valid(dsl, properties)
|
|
43
|
+
key_value = get_key_value(dsl)
|
|
44
|
+
return false unless key_value
|
|
45
|
+
|
|
46
|
+
operator = key_value[:key]
|
|
47
|
+
sub_dsl = key_value[:value]
|
|
48
|
+
|
|
49
|
+
case operator
|
|
50
|
+
when SegmentOperatorValueEnum::NOT
|
|
51
|
+
!is_segmentation_valid(sub_dsl, properties)
|
|
52
|
+
when SegmentOperatorValueEnum::AND
|
|
53
|
+
every(sub_dsl, properties)
|
|
54
|
+
when SegmentOperatorValueEnum::OR
|
|
55
|
+
some(sub_dsl, properties)
|
|
56
|
+
when SegmentOperatorValueEnum::CUSTOM_VARIABLE
|
|
57
|
+
SegmentOperandEvaluator.new.evaluate_custom_variable_dsl(sub_dsl, properties)
|
|
58
|
+
when SegmentOperatorValueEnum::USER
|
|
59
|
+
SegmentOperandEvaluator.new.evaluate_user_dsl(sub_dsl, properties)
|
|
60
|
+
when SegmentOperatorValueEnum::UA
|
|
61
|
+
SegmentOperandEvaluator.new.evaluate_user_agent_dsl(sub_dsl, @context)
|
|
62
|
+
when SegmentOperatorValueEnum::IP
|
|
63
|
+
SegmentOperandEvaluator.new.evaluate_string_operand_dsl(sub_dsl, @context, SegmentOperatorValueEnum::IP)
|
|
64
|
+
when SegmentOperatorValueEnum::BROWSER_VERSION
|
|
65
|
+
SegmentOperandEvaluator.new.evaluate_string_operand_dsl(sub_dsl, @context, SegmentOperatorValueEnum::BROWSER_VERSION)
|
|
66
|
+
when SegmentOperatorValueEnum::OS_VERSION
|
|
67
|
+
SegmentOperandEvaluator.new.evaluate_string_operand_dsl(sub_dsl, @context, SegmentOperatorValueEnum::OS_VERSION)
|
|
68
|
+
else
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Evaluates if any of the DSL nodes are valid using the OR logic.
|
|
74
|
+
# @param dsl_nodes [Array<Hash>] The DSL nodes to evaluate
|
|
75
|
+
# @param custom_variables [Hash] The custom variables
|
|
76
|
+
# @return [Boolean] True if any of the DSL nodes are valid, false otherwise
|
|
77
|
+
def some(dsl_nodes, custom_variables)
|
|
78
|
+
ua_parser_map = {}
|
|
79
|
+
key_count = 0
|
|
80
|
+
is_ua_parser = false
|
|
81
|
+
|
|
82
|
+
dsl_nodes.each do |dsl|
|
|
83
|
+
dsl.each do |key, value|
|
|
84
|
+
if [SegmentOperatorValueEnum::OPERATING_SYSTEM, SegmentOperatorValueEnum::BROWSER_AGENT,
|
|
85
|
+
SegmentOperatorValueEnum::DEVICE_TYPE, SegmentOperatorValueEnum::DEVICE].include?(key)
|
|
86
|
+
is_ua_parser = true
|
|
87
|
+
ua_parser_map[key] ||= []
|
|
88
|
+
ua_parser_map[key] += Array(value).map(&:to_s)
|
|
89
|
+
key_count += 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if key == SegmentOperatorValueEnum::FEATURE_ID
|
|
93
|
+
feature_id_object = dsl[key]
|
|
94
|
+
feature_id_key = feature_id_object.keys.first
|
|
95
|
+
feature_id_value = feature_id_object[feature_id_key]
|
|
96
|
+
|
|
97
|
+
if %w[on off].include?(feature_id_value)
|
|
98
|
+
feature = @settings.get_features.find { |f| f.get_id == feature_id_key.to_i }
|
|
99
|
+
if feature
|
|
100
|
+
feature_key = feature.get_key
|
|
101
|
+
result = check_in_user_storage(@settings, feature_key, @context)
|
|
102
|
+
return !result if feature_id_value == 'off'
|
|
103
|
+
return result
|
|
104
|
+
else
|
|
105
|
+
LoggerService.log(LogLevelEnum::INFO, "Feature not found with featureIdKey: #{feature_id_key}", nil)
|
|
106
|
+
return nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
return check_user_agent_parser(ua_parser_map) if is_ua_parser && key_count == dsl_nodes.length
|
|
113
|
+
return true if is_segmentation_valid(dsl, custom_variables)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Evaluates all DSL nodes using the AND logic.
|
|
120
|
+
# @param dsl_nodes [Array<Hash>] The DSL nodes to evaluate
|
|
121
|
+
# @param custom_variables [Hash] The custom variables
|
|
122
|
+
# @return [Boolean] True if all DSL nodes are valid, false otherwise
|
|
123
|
+
def every(dsl_nodes, custom_variables)
|
|
124
|
+
location_map = {}
|
|
125
|
+
dsl_nodes.each do |dsl|
|
|
126
|
+
if dsl.keys.any? { |key| [SegmentOperatorValueEnum::COUNTRY, SegmentOperatorValueEnum::REGION,
|
|
127
|
+
SegmentOperatorValueEnum::CITY].include?(key) }
|
|
128
|
+
add_location_values_to_map(dsl, location_map)
|
|
129
|
+
return check_location_pre_segmentation(location_map) if location_map.keys.length == dsl_nodes.length
|
|
130
|
+
next
|
|
131
|
+
end
|
|
132
|
+
return false unless is_segmentation_valid(dsl, custom_variables)
|
|
133
|
+
end
|
|
134
|
+
true
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Adds the location values to the map
|
|
138
|
+
# @param dsl [Hash] The DSL node
|
|
139
|
+
# @param location_map [Hash] The location map
|
|
140
|
+
def add_location_values_to_map(dsl, location_map)
|
|
141
|
+
location_map[SegmentOperatorValueEnum::COUNTRY] = dsl[SegmentOperatorValueEnum::COUNTRY] if dsl.key?(SegmentOperatorValueEnum::COUNTRY)
|
|
142
|
+
location_map[SegmentOperatorValueEnum::REGION] = dsl[SegmentOperatorValueEnum::REGION] if dsl.key?(SegmentOperatorValueEnum::REGION)
|
|
143
|
+
location_map[SegmentOperatorValueEnum::CITY] = dsl[SegmentOperatorValueEnum::CITY] if dsl.key?(SegmentOperatorValueEnum::CITY)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Checks if the location pre-segmentation is valid
|
|
147
|
+
# @param location_map [Hash] The location map
|
|
148
|
+
# @return [Boolean] True if the location pre-segmentation is valid, false otherwise
|
|
149
|
+
def check_location_pre_segmentation(location_map)
|
|
150
|
+
unless @context&.get_ip_address
|
|
151
|
+
LoggerService.log(LogLevelEnum::ERROR, 'INVALID_IP_ADDRESS_IN_CONTEXT_FOR_PRE_SEGMENTATION', { an: ApiEnum::GET_FLAG, sId: @context.get_session_id, uuid: @context.get_uuid})
|
|
152
|
+
return false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
unless @context&.get_vwo&.get_location
|
|
156
|
+
return false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
values_match(location_map, @context.get_vwo.get_location)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Checks if the user agent parser is valid
|
|
163
|
+
# @param ua_parser_map [Hash] The user agent parser map
|
|
164
|
+
# @return [Boolean] True if the user agent parser is valid, false otherwise
|
|
165
|
+
def check_user_agent_parser(ua_parser_map)
|
|
166
|
+
unless @context&.get_user_agent
|
|
167
|
+
LoggerService.log(LogLevelEnum::ERROR, 'INVALID_USER_AGENT_IN_CONTEXT_FOR_PRE_SEGMENTATION', { an: ApiEnum::GET_FLAG, sId: @context.get_session_id, uuid: @context.get_uuid})
|
|
168
|
+
return false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
unless @context&.get_vwo&.get_ua_info
|
|
172
|
+
return false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
check_value_present(ua_parser_map, @context.get_vwo.get_ua_info)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Checks if the feature key is present in the user's storage
|
|
179
|
+
# @param settings [SettingsModel] The settings for the VWO instance
|
|
180
|
+
# @param feature_key [String] The key of the feature to check
|
|
181
|
+
# @param context [ContextModel] The context for the evaluation
|
|
182
|
+
# @return [Boolean] True if the feature key is present in the user's storage, false otherwise
|
|
183
|
+
def check_in_user_storage(settings, feature_key, context)
|
|
184
|
+
storage_service = StorageService.new
|
|
185
|
+
stored_data = StorageDecorator.new.get_feature_from_storage(feature_key, context, storage_service)
|
|
186
|
+
stored_data.is_a?(Hash) && !stored_data.empty?
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Checks if the expected values are present in the actual values
|
|
190
|
+
# @param expected_map [Hash] The expected values
|
|
191
|
+
# @param actual_map [Hash] The actual values
|
|
192
|
+
# @return [Boolean] True if the expected values are present in the actual values, false otherwise
|
|
193
|
+
def check_value_present(expected_map, actual_map)
|
|
194
|
+
actual_map.each do |key, actual_value|
|
|
195
|
+
next unless expected_map.key?(key)
|
|
196
|
+
|
|
197
|
+
expected_values = expected_map[key].map(&:downcase)
|
|
198
|
+
|
|
199
|
+
return true if expected_values.any? { |val| val.start_with?('wildcard(') && val.end_with?(')') && actual_value.match?(Regexp.new(val[9..-2].gsub('*', '.*'), Regexp::IGNORECASE)) }
|
|
200
|
+
return true if expected_values.include?(actual_value.downcase)
|
|
201
|
+
end
|
|
202
|
+
false
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Checks if the expected location values match the user's location
|
|
206
|
+
# @param expected_location_map [Hash] The expected location values
|
|
207
|
+
# @param user_location [Hash] The user's location values
|
|
208
|
+
# @return [Boolean] True if the expected location values match the user's location, false otherwise
|
|
209
|
+
def values_match(expected_location_map, user_location)
|
|
210
|
+
expected_location_map.all? { |key, value| normalize_value(value) == normalize_value(user_location[key]) }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def normalize_value(value)
|
|
214
|
+
return nil if value.nil?
|
|
215
|
+
|
|
216
|
+
value.to_s.gsub(/^"|"$/, '').strip
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require_relative '../utils/segment_util'
|
|
16
|
+
require_relative '../enums/segment_operand_value_enum'
|
|
17
|
+
require_relative '../enums/segment_operand_regex_enum'
|
|
18
|
+
require_relative '../enums/segment_operator_value_enum'
|
|
19
|
+
require_relative '../../../utils/data_type_util'
|
|
20
|
+
require_relative '../../../utils/gateway_service_util'
|
|
21
|
+
require_relative '../../../enums/url_enum'
|
|
22
|
+
require_relative '../../../services/logger_service'
|
|
23
|
+
require_relative '../../../models/user/context_model'
|
|
24
|
+
require_relative '../../../enums/log_level_enum'
|
|
25
|
+
require_relative '../../../enums/api_enum'
|
|
26
|
+
|
|
27
|
+
# SegmentOperandEvaluator class provides methods to evaluate different types of DSL (Domain Specific Language)
|
|
28
|
+
# expressions based on the segment conditions defined for custom variables, user IDs, and user agents.
|
|
29
|
+
class SegmentOperandEvaluator
|
|
30
|
+
# Regex pattern to check if a string contains non-numeric characters (except decimal point)
|
|
31
|
+
NON_NUMERIC_PATTERN = /[^0-9.]/
|
|
32
|
+
|
|
33
|
+
# Evaluates the custom variable DSL
|
|
34
|
+
# @param dsl_operand_value [Hash] The operand value to evaluate
|
|
35
|
+
# @param properties [Hash] The properties to evaluate the operand against
|
|
36
|
+
# @return [Boolean] True if the operand value matches the tag value, false otherwise
|
|
37
|
+
def evaluate_custom_variable_dsl(dsl_operand_value, properties)
|
|
38
|
+
key_value = get_key_value(dsl_operand_value)
|
|
39
|
+
return false unless key_value
|
|
40
|
+
|
|
41
|
+
operand_key = key_value[:key].to_sym
|
|
42
|
+
operand = key_value[:value]
|
|
43
|
+
|
|
44
|
+
return false unless properties.key?(operand_key)
|
|
45
|
+
|
|
46
|
+
if operand.include?('inlist')
|
|
47
|
+
match = operand.match(/inlist\(([^)]+)\)/)
|
|
48
|
+
unless match
|
|
49
|
+
LoggerService.log(LogLevelEnum::ERROR, "INVALID_ATTRIBUTE_LIST_FORMAT", { an: ApiEnum::GET_FLAG, sId: @context.get_session_id, uuid: @context.get_uuid})
|
|
50
|
+
return false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
tag_value = properties[operand_key]
|
|
54
|
+
attribute_value = pre_process_tag_value(tag_value)
|
|
55
|
+
list_id = match[1]
|
|
56
|
+
|
|
57
|
+
query_params_obj = { attribute: attribute_value, listId: list_id }
|
|
58
|
+
|
|
59
|
+
begin
|
|
60
|
+
res = get_from_gateway_service(query_params_obj, UrlEnum::ATTRIBUTE_CHECK)
|
|
61
|
+
if res.nil? || res == false || res == 'false' || (res.is_a?(Hash) && res[:status] == 0)
|
|
62
|
+
return false
|
|
63
|
+
end
|
|
64
|
+
return res
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
LoggerService.log(LogLevelEnum::ERROR, "ERROR_FETCHING_DATA_FROM_GATEWAY", { err: e.message, an: ApiEnum::GET_FLAG, sId: @context.get_session_id, uuid: @context.get_uuid})
|
|
67
|
+
return false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
false
|
|
71
|
+
else
|
|
72
|
+
tag_value = properties[operand_key]
|
|
73
|
+
tag_value = pre_process_tag_value(tag_value)
|
|
74
|
+
processed_operand = pre_process_operand_value(operand)
|
|
75
|
+
processed_values = process_values(processed_operand[:operand_value], tag_value)
|
|
76
|
+
tag_value = processed_values[:tag_value]
|
|
77
|
+
extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Evaluates the user DSL
|
|
82
|
+
# @param dsl_operand_value [String] The operand value to evaluate
|
|
83
|
+
# @param properties [Hash] The properties to evaluate the operand against
|
|
84
|
+
# @return [Boolean] True if the operand value matches the tag value, false otherwise
|
|
85
|
+
def evaluate_user_dsl(dsl_operand_value, properties)
|
|
86
|
+
users = dsl_operand_value.split(',')
|
|
87
|
+
users.any? { |user| user.strip == properties["_vwoUserId"].to_s }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Evaluates the user agent DSL
|
|
91
|
+
# @param dsl_operand_value [String] The operand value to evaluate
|
|
92
|
+
# @param context [ContextModel] The context to evaluate the operand against
|
|
93
|
+
# @return [Boolean] True if the operand value matches the tag value, false otherwise
|
|
94
|
+
def evaluate_user_agent_dsl(dsl_operand_value, context)
|
|
95
|
+
operand = dsl_operand_value
|
|
96
|
+
unless context.get_user_agent
|
|
97
|
+
LoggerService.log(LogLevelEnum::INFO, 'To Evaluate UserAgent segmentation, please provide userAgent in context', nil)
|
|
98
|
+
return false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
tag_value = CGI.unescape(context.get_user_agent)
|
|
102
|
+
processed_operand = pre_process_operand_value(operand)
|
|
103
|
+
processed_values = process_values(processed_operand[:operand_value], tag_value)
|
|
104
|
+
tag_value = processed_values[:tag_value]
|
|
105
|
+
extract_result(processed_operand[:operand_type], processed_values[:operand_value], tag_value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Evaluates a given string tag value against a DSL operand value.
|
|
109
|
+
# @param dsl_operand_value [String] The DSL operand string (e.g., "contains(\"value\")").
|
|
110
|
+
# @param context [ContextModel] The context object containing the value to evaluate.
|
|
111
|
+
# @param operand_type [String] The type of operand being evaluated (ip_address, browser_version, os_version).
|
|
112
|
+
# @return [Boolean] True if tag value matches DSL operand criteria, false otherwise.
|
|
113
|
+
def evaluate_string_operand_dsl(dsl_operand_value, context, operand_type)
|
|
114
|
+
operand = dsl_operand_value.to_s
|
|
115
|
+
|
|
116
|
+
# Determine the tag value based on operand type
|
|
117
|
+
tag_value = get_tag_value_for_operand_type(context, operand_type)
|
|
118
|
+
|
|
119
|
+
if tag_value.nil?
|
|
120
|
+
log_missing_context_error(operand_type)
|
|
121
|
+
return false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
operand_type_and_value = pre_process_operand_value(operand)
|
|
125
|
+
processed_values = process_values(operand_type_and_value[:operand_value], tag_value, operand_type)
|
|
126
|
+
processed_tag_value = processed_values[:tag_value]
|
|
127
|
+
|
|
128
|
+
extract_result(
|
|
129
|
+
operand_type_and_value[:operand_type],
|
|
130
|
+
processed_values[:operand_value].to_s.strip.gsub(/"/, ''),
|
|
131
|
+
processed_tag_value
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Evaluates IP address DSL expression.
|
|
136
|
+
# @param dsl_operand_value [String] The DSL expression for the IP address.
|
|
137
|
+
# @param context [ContextModel] The context object containing the IP address.
|
|
138
|
+
# @return [Boolean] True if the IP address matches the DSL condition, otherwise false.
|
|
139
|
+
def evaluate_ip_dsl(dsl_operand_value, context)
|
|
140
|
+
evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::IP)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Evaluates browser version DSL expression.
|
|
144
|
+
# @param dsl_operand_value [String] The DSL expression for the browser version.
|
|
145
|
+
# @param context [ContextModel] The context object containing the user agent info.
|
|
146
|
+
# @return [Boolean] True if the browser version matches the DSL condition, otherwise false.
|
|
147
|
+
def evaluate_browser_version_dsl(dsl_operand_value, context)
|
|
148
|
+
evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::BROWSER_VERSION)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Evaluates OS version DSL expression.
|
|
152
|
+
# @param dsl_operand_value [String] The DSL expression for the OS version.
|
|
153
|
+
# @param context [ContextModel] The context object containing the user agent info.
|
|
154
|
+
# @return [Boolean] True if the OS version matches the DSL condition, otherwise false.
|
|
155
|
+
def evaluate_os_version_dsl(dsl_operand_value, context)
|
|
156
|
+
evaluate_string_operand_dsl(dsl_operand_value, context, SegmentOperatorValueEnum::OS_VERSION)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Gets the appropriate tag value based on the operand type.
|
|
160
|
+
# @param context [ContextModel] The context object.
|
|
161
|
+
# @param operand_type [String] The type of operand.
|
|
162
|
+
# @return [String, nil] The tag value or nil if not available.
|
|
163
|
+
def get_tag_value_for_operand_type(context, operand_type)
|
|
164
|
+
case operand_type
|
|
165
|
+
when SegmentOperatorValueEnum::IP
|
|
166
|
+
context.get_ip_address
|
|
167
|
+
when SegmentOperatorValueEnum::BROWSER_VERSION
|
|
168
|
+
get_browser_version_from_context(context)
|
|
169
|
+
else
|
|
170
|
+
# Default works for OS version
|
|
171
|
+
get_os_version_from_context(context)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Gets browser version from context.
|
|
176
|
+
# @param context [ContextModel] The context object.
|
|
177
|
+
# @return [String, nil] The browser version or nil if not available.
|
|
178
|
+
def get_browser_version_from_context(context)
|
|
179
|
+
user_agent = context.get_vwo&.get_ua_info
|
|
180
|
+
return nil unless user_agent && user_agent.is_a?(Hash) && !user_agent.empty?
|
|
181
|
+
|
|
182
|
+
# Assuming UserAgent dictionary contains browser_version
|
|
183
|
+
if user_agent.key?('browser_version')
|
|
184
|
+
return user_agent['browser_version']&.to_s
|
|
185
|
+
end
|
|
186
|
+
nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Gets OS version from context.
|
|
190
|
+
# @param context [ContextModel] The context object.
|
|
191
|
+
# @return [String, nil] The OS version or nil if not available.
|
|
192
|
+
def get_os_version_from_context(context)
|
|
193
|
+
user_agent = context.get_vwo&.get_ua_info
|
|
194
|
+
return nil unless user_agent && user_agent.is_a?(Hash) && !user_agent.empty?
|
|
195
|
+
|
|
196
|
+
# Assuming UserAgent dictionary contains os_version
|
|
197
|
+
if user_agent.key?('os_version')
|
|
198
|
+
return user_agent['os_version']&.to_s
|
|
199
|
+
end
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Logs appropriate error message for missing context.
|
|
204
|
+
# @param operand_type [String] The type of operand.
|
|
205
|
+
def log_missing_context_error(operand_type)
|
|
206
|
+
case operand_type
|
|
207
|
+
when SegmentOperatorValueEnum::IP
|
|
208
|
+
LoggerService.log(LogLevelEnum::INFO, 'To evaluate IP segmentation, please provide ipAddress in context', nil)
|
|
209
|
+
when SegmentOperatorValueEnum::BROWSER_VERSION
|
|
210
|
+
LoggerService.log(LogLevelEnum::INFO, 'To evaluate browser version segmentation, please provide userAgent in context', nil)
|
|
211
|
+
else
|
|
212
|
+
LoggerService.log(LogLevelEnum::INFO, 'To evaluate OS version segmentation, please provide userAgent in context', nil)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Pre-processes the tag value to ensure it is in the correct format for evaluation.
|
|
217
|
+
# @param tag_value [Any] The value to be processed.
|
|
218
|
+
# @return [String, Boolean] The processed tag value, either as a string or a boolean.
|
|
219
|
+
def pre_process_tag_value(tag_value)
|
|
220
|
+
# Default to empty string if undefined
|
|
221
|
+
if tag_value.nil?
|
|
222
|
+
tag_value = ''
|
|
223
|
+
end
|
|
224
|
+
# Convert boolean values to boolean type
|
|
225
|
+
if DataTypeUtil.is_boolean(tag_value)
|
|
226
|
+
tag_value = tag_value ? true : false
|
|
227
|
+
end
|
|
228
|
+
# Convert all non-null values to string
|
|
229
|
+
unless tag_value.nil?
|
|
230
|
+
tag_value = tag_value.to_s
|
|
231
|
+
end
|
|
232
|
+
tag_value
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Pre-processes the operand value
|
|
236
|
+
# @param operand [String] The operand to pre-process
|
|
237
|
+
# @return [Hash] The pre-processed operand value
|
|
238
|
+
def pre_process_operand_value(operand)
|
|
239
|
+
case operand
|
|
240
|
+
when /#{SegmentOperandRegexEnum::LOWER_MATCH}/
|
|
241
|
+
{ operand_type: SegmentOperandValueEnum::LOWER_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LOWER_MATCH) }
|
|
242
|
+
when /#{SegmentOperandRegexEnum::WILDCARD_MATCH}/
|
|
243
|
+
value = extract_operand_value(operand, SegmentOperandRegexEnum::WILDCARD_MATCH)
|
|
244
|
+
starting_star = match_with_regex(value, SegmentOperandRegexEnum::STARTING_STAR)
|
|
245
|
+
ending_star = match_with_regex(value, SegmentOperandRegexEnum::ENDING_STAR)
|
|
246
|
+
|
|
247
|
+
# Determine specific wildcard type
|
|
248
|
+
if starting_star && ending_star
|
|
249
|
+
type = SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
|
|
250
|
+
elsif starting_star
|
|
251
|
+
type = SegmentOperandValueEnum::STARTING_STAR_VALUE
|
|
252
|
+
elsif ending_star
|
|
253
|
+
type = SegmentOperandValueEnum::ENDING_STAR_VALUE
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Remove wildcard characters from the operand value
|
|
257
|
+
value = value
|
|
258
|
+
.gsub(Regexp.new(SegmentOperandRegexEnum::STARTING_STAR), '')
|
|
259
|
+
.gsub(Regexp.new(SegmentOperandRegexEnum::ENDING_STAR), '')
|
|
260
|
+
|
|
261
|
+
{ operand_type: type, operand_value: value }
|
|
262
|
+
when /#{SegmentOperandRegexEnum::REGEX_MATCH}/
|
|
263
|
+
{ operand_type: SegmentOperandValueEnum::REGEX_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::REGEX_MATCH) }
|
|
264
|
+
when /#{SegmentOperandRegexEnum::GREATER_THAN_MATCH}/
|
|
265
|
+
{ operand_type: SegmentOperandValueEnum::GREATER_THAN_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::GREATER_THAN_MATCH) }
|
|
266
|
+
when /#{SegmentOperandRegexEnum::LESS_THAN_MATCH}/
|
|
267
|
+
{ operand_type: SegmentOperandValueEnum::LESS_THAN_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LESS_THAN_MATCH) }
|
|
268
|
+
when /#{SegmentOperandRegexEnum::GREATER_THAN_EQUAL_TO_MATCH}/
|
|
269
|
+
{ operand_type: SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::GREATER_THAN_EQUAL_TO_MATCH) }
|
|
270
|
+
when /#{SegmentOperandRegexEnum::LESS_THAN_EQUAL_TO_MATCH}/
|
|
271
|
+
{ operand_type: SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE, operand_value: extract_operand_value(operand, SegmentOperandRegexEnum::LESS_THAN_EQUAL_TO_MATCH) }
|
|
272
|
+
else
|
|
273
|
+
{ operand_type: SegmentOperandValueEnum::EQUAL_VALUE, operand_value: operand }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Extracts the operand value from the operand
|
|
278
|
+
# @param operand [String] The operand to extract the value from
|
|
279
|
+
# @param regex [String] The regex to match the operand against
|
|
280
|
+
# @return [String] The extracted operand value
|
|
281
|
+
def extract_operand_value(operand, regex)
|
|
282
|
+
match_result = match_with_regex(operand, regex)
|
|
283
|
+
match_result && match_result[1] ? match_result[1] : ''
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Processes numeric values from operand and tag values, converting them to strings.
|
|
287
|
+
# @param operand_value [Any] The operand value to process.
|
|
288
|
+
# @param tag_value [Any] The tag value to process.
|
|
289
|
+
# @param operand_type [String] The type of operand being evaluated (optional).
|
|
290
|
+
# @return [Hash] An object containing the processed operand and tag values as strings.
|
|
291
|
+
def process_values(operand_value, tag_value, operand_type = nil)
|
|
292
|
+
if [SegmentOperatorValueEnum::IP, SegmentOperatorValueEnum::BROWSER_VERSION, SegmentOperatorValueEnum::OS_VERSION].include?(operand_type)
|
|
293
|
+
return {
|
|
294
|
+
operand_value: operand_value,
|
|
295
|
+
tag_value: tag_value
|
|
296
|
+
}
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Convert operand and tag values to floats
|
|
300
|
+
if NON_NUMERIC_PATTERN.match?(tag_value.to_s)
|
|
301
|
+
return {
|
|
302
|
+
operand_value: operand_value,
|
|
303
|
+
tag_value: tag_value
|
|
304
|
+
}
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
processed_operand_value = operand_value.to_f
|
|
308
|
+
processed_tag_value = tag_value.to_f
|
|
309
|
+
|
|
310
|
+
# Return original values if conversion fails
|
|
311
|
+
if processed_operand_value == 0 && operand_value.to_s != '0' && operand_value.to_s != '0.0'
|
|
312
|
+
return {
|
|
313
|
+
operand_value: operand_value,
|
|
314
|
+
tag_value: tag_value
|
|
315
|
+
}
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
if processed_tag_value == 0 && tag_value.to_s != '0' && tag_value.to_s != '0.0'
|
|
319
|
+
return {
|
|
320
|
+
operand_value: operand_value,
|
|
321
|
+
tag_value: tag_value
|
|
322
|
+
}
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Convert numeric values back to strings
|
|
326
|
+
{
|
|
327
|
+
operand_value: processed_operand_value.to_s,
|
|
328
|
+
tag_value: processed_tag_value.to_s
|
|
329
|
+
}
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Extracts the result from the operand value and tag value
|
|
333
|
+
# @param operand_type [Symbol] The type of operand
|
|
334
|
+
# @param operand_value [String] The value of the operand
|
|
335
|
+
# @param tag_value [String] The value of the tag
|
|
336
|
+
# @return [Boolean] True if the operand value matches the tag value, false otherwise
|
|
337
|
+
def extract_result(operand_type, operand_value, tag_value)
|
|
338
|
+
result = false
|
|
339
|
+
|
|
340
|
+
return false if tag_value.nil?
|
|
341
|
+
|
|
342
|
+
# Ensure operand_value and tag_value are strings
|
|
343
|
+
operand_value_str = operand_value.to_s
|
|
344
|
+
tag_value_str = tag_value.to_s
|
|
345
|
+
|
|
346
|
+
case operand_type
|
|
347
|
+
when SegmentOperandValueEnum::LOWER_VALUE
|
|
348
|
+
result = operand_value_str.downcase == tag_value_str.downcase
|
|
349
|
+
when SegmentOperandValueEnum::STARTING_ENDING_STAR_VALUE
|
|
350
|
+
result = tag_value_str.include?(operand_value_str)
|
|
351
|
+
when SegmentOperandValueEnum::STARTING_STAR_VALUE
|
|
352
|
+
result = tag_value_str.end_with?(operand_value_str)
|
|
353
|
+
when SegmentOperandValueEnum::ENDING_STAR_VALUE
|
|
354
|
+
result = tag_value_str.start_with?(operand_value_str)
|
|
355
|
+
when SegmentOperandValueEnum::REGEX_VALUE
|
|
356
|
+
begin
|
|
357
|
+
pattern = Regexp.new(operand_value_str)
|
|
358
|
+
result = pattern.match?(tag_value_str)
|
|
359
|
+
rescue StandardError
|
|
360
|
+
result = false
|
|
361
|
+
end
|
|
362
|
+
when SegmentOperandValueEnum::GREATER_THAN_VALUE
|
|
363
|
+
result = compare_versions(tag_value_str, operand_value_str) > 0
|
|
364
|
+
when SegmentOperandValueEnum::GREATER_THAN_EQUAL_TO_VALUE
|
|
365
|
+
result = compare_versions(tag_value_str, operand_value_str) >= 0
|
|
366
|
+
when SegmentOperandValueEnum::LESS_THAN_VALUE
|
|
367
|
+
result = compare_versions(tag_value_str, operand_value_str) < 0
|
|
368
|
+
when SegmentOperandValueEnum::LESS_THAN_EQUAL_TO_VALUE
|
|
369
|
+
result = compare_versions(tag_value_str, operand_value_str) <= 0
|
|
370
|
+
else
|
|
371
|
+
# For version-like strings, use version comparison; otherwise use string comparison
|
|
372
|
+
if version_string?(tag_value_str) && version_string?(operand_value_str)
|
|
373
|
+
result = compare_versions(tag_value_str, operand_value_str) == 0
|
|
374
|
+
else
|
|
375
|
+
result = tag_value_str == operand_value_str
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
result
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Checks if a string appears to be a version string (contains only digits and dots).
|
|
383
|
+
# @param str [String] The string to check.
|
|
384
|
+
# @return [Boolean] True if the string appears to be a version string.
|
|
385
|
+
def version_string?(str)
|
|
386
|
+
/^(\d+\.)*\d+$/.match?(str)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Compares two version strings using semantic versioning rules.
|
|
390
|
+
# Supports formats like "1.2.3", "1.0", "2.1.4.5", etc.
|
|
391
|
+
# @param version1 [String] First version string.
|
|
392
|
+
# @param version2 [String] Second version string.
|
|
393
|
+
# @return [Integer] -1 if version1 < version2, 0 if equal, 1 if version1 > version2.
|
|
394
|
+
def compare_versions(version1, version2)
|
|
395
|
+
# Split versions by dots and convert to integers
|
|
396
|
+
parts1 = version1.split('.').map { |part| part.match?(/^\d+$/) ? part.to_i : 0 }
|
|
397
|
+
parts2 = version2.split('.').map { |part| part.match?(/^\d+$/) ? part.to_i : 0 }
|
|
398
|
+
|
|
399
|
+
# Find the maximum length to handle different version formats
|
|
400
|
+
max_length = [parts1.length, parts2.length].max
|
|
401
|
+
|
|
402
|
+
(0...max_length).each do |i|
|
|
403
|
+
part1 = i < parts1.length ? parts1[i] : 0
|
|
404
|
+
part2 = i < parts2.length ? parts2[i] : 0
|
|
405
|
+
|
|
406
|
+
if part1 < part2
|
|
407
|
+
return -1
|
|
408
|
+
elsif part1 > part2
|
|
409
|
+
return 1
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
0 # Versions are equal
|
|
414
|
+
end
|
|
415
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require 'json'
|
|
16
|
+
|
|
17
|
+
# Utility function to check if a value is an object (excluding arrays and other types)
|
|
18
|
+
def is_object?(val)
|
|
19
|
+
val.is_a?(Hash)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Extracts the first key-value pair from the provided object.
|
|
23
|
+
# @param obj [Hash] The object from which to extract the key-value pair.
|
|
24
|
+
# @return [Hash, nil] A hash containing the first key and value, or nil if input is not a hash.
|
|
25
|
+
def get_key_value(obj)
|
|
26
|
+
return nil unless is_object?(obj)
|
|
27
|
+
|
|
28
|
+
key = obj.keys.first
|
|
29
|
+
return nil unless key
|
|
30
|
+
|
|
31
|
+
{ key: key, value: obj[key] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Matches a string against a regular expression and returns the match result.
|
|
35
|
+
# @param string [String] The string to match against the regex.
|
|
36
|
+
# @param regex [String] The regex pattern as a string.
|
|
37
|
+
# @return [Array, nil] The results of the regex match, or nil if an error occurs.
|
|
38
|
+
def match_with_regex(string, regex)
|
|
39
|
+
begin
|
|
40
|
+
return string.match(Regexp.new(regex))
|
|
41
|
+
rescue StandardError
|
|
42
|
+
return nil
|
|
43
|
+
end
|
|
44
|
+
end
|