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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/resources/debug_messages.json +21 -0
  3. data/lib/resources/error_messages.json +63 -0
  4. data/lib/resources/info_messages.json +43 -0
  5. data/lib/resources/warn_messages.json +1 -0
  6. data/lib/vwo.rb +40 -0
  7. data/lib/wingify/api/get_flag.rb +244 -0
  8. data/lib/wingify/api/set_attribute.rb +57 -0
  9. data/lib/wingify/api/track_event.rb +80 -0
  10. data/lib/wingify/constants/constants.rb +106 -0
  11. data/lib/wingify/decorators/storage_decorator.rb +82 -0
  12. data/lib/wingify/enums/api_enum.rb +23 -0
  13. data/lib/wingify/enums/campaign_type_enum.rb +19 -0
  14. data/lib/wingify/enums/debug_category_enum.rb +20 -0
  15. data/lib/wingify/enums/decision_types_enum.rb +18 -0
  16. data/lib/wingify/enums/event_enum.rb +22 -0
  17. data/lib/wingify/enums/headers_enum.rb +20 -0
  18. data/lib/wingify/enums/hooks_enum.rb +17 -0
  19. data/lib/wingify/enums/http_method_enum.rb +18 -0
  20. data/lib/wingify/enums/log_level_enum.rb +21 -0
  21. data/lib/wingify/enums/log_level_to_number.rb +27 -0
  22. data/lib/wingify/enums/status_enum.rb +19 -0
  23. data/lib/wingify/enums/storage_enum.rb +22 -0
  24. data/lib/wingify/enums/url_enum.rb +22 -0
  25. data/lib/wingify/models/campaign/campaign_model.rb +192 -0
  26. data/lib/wingify/models/campaign/feature_model.rb +111 -0
  27. data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
  28. data/lib/wingify/models/campaign/metric_model.rb +44 -0
  29. data/lib/wingify/models/campaign/rule_model.rb +56 -0
  30. data/lib/wingify/models/campaign/variable_model.rb +51 -0
  31. data/lib/wingify/models/campaign/variation_model.rb +137 -0
  32. data/lib/wingify/models/gateway_service_model.rb +39 -0
  33. data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
  34. data/lib/wingify/models/settings/settings_model.rb +101 -0
  35. data/lib/wingify/models/storage/storage_data_model.rb +44 -0
  36. data/lib/wingify/models/user/context_model.rb +154 -0
  37. data/lib/wingify/models/user/context_vwo_model.rb +38 -0
  38. data/lib/wingify/models/user/get_flag_response.rb +50 -0
  39. data/lib/wingify/models/vwo_options_model.rb +129 -0
  40. data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
  41. data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
  42. data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
  43. data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
  44. data/lib/wingify/packages/logger/logger.rb +38 -0
  45. data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
  46. data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
  47. data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
  48. data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
  49. data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
  50. data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
  51. data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
  52. data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
  53. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
  54. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
  55. data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
  56. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
  57. data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
  58. data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
  59. data/lib/wingify/packages/storage/connector.rb +26 -0
  60. data/lib/wingify/packages/storage/storage.rb +47 -0
  61. data/lib/wingify/services/batch_event_queue.rb +179 -0
  62. data/lib/wingify/services/campaign_decision_service.rb +161 -0
  63. data/lib/wingify/services/hooks_service.rb +51 -0
  64. data/lib/wingify/services/logger_service.rb +114 -0
  65. data/lib/wingify/services/settings_service.rb +178 -0
  66. data/lib/wingify/services/storage_service.rb +66 -0
  67. data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
  68. data/lib/wingify/utils/brand_context.rb +33 -0
  69. data/lib/wingify/utils/brand_util.rb +53 -0
  70. data/lib/wingify/utils/campaign_util.rb +284 -0
  71. data/lib/wingify/utils/data_type_util.rb +105 -0
  72. data/lib/wingify/utils/debugger_service_util.rb +40 -0
  73. data/lib/wingify/utils/decision_util.rb +259 -0
  74. data/lib/wingify/utils/event_util.rb +55 -0
  75. data/lib/wingify/utils/function_util.rb +141 -0
  76. data/lib/wingify/utils/gateway_service_util.rb +101 -0
  77. data/lib/wingify/utils/impression_util.rb +66 -0
  78. data/lib/wingify/utils/log_message_util.rb +42 -0
  79. data/lib/wingify/utils/meg_util.rb +357 -0
  80. data/lib/wingify/utils/network_util.rb +503 -0
  81. data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
  82. data/lib/wingify/utils/settings_util.rb +38 -0
  83. data/lib/wingify/utils/usage_stats_util.rb +119 -0
  84. data/lib/wingify/utils/uuid_util.rb +96 -0
  85. data/lib/wingify/wingify_builder.rb +261 -0
  86. data/lib/wingify/wingify_client.rb +227 -0
  87. data/lib/wingify.rb +117 -0
  88. metadata +327 -0
@@ -0,0 +1,55 @@
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 './network_util'
16
+ require_relative '../enums/event_enum'
17
+ require_relative '../services/batch_event_queue'
18
+
19
+ # Sends an init called event to VWO.
20
+ # This event is triggered when the init function is called.
21
+ # @param settings_fetch_time [Number] Time taken to fetch settings in milliseconds.
22
+ # @param sdk_init_time [Number] Time taken to initialize the SDK in milliseconds.
23
+ def send_sdk_init_event(settings_fetch_time, sdk_init_time)
24
+ # Get base properties for the event
25
+ properties = NetworkUtil.get_events_base_properties(EventEnum::INIT_CALLED)
26
+ payload = NetworkUtil.get_sdk_init_event_payload(EventEnum::INIT_CALLED, settings_fetch_time, sdk_init_time)
27
+
28
+ # check if batching is enabled
29
+ if BatchEventsQueue.instance
30
+ # add the payload to the batch events queue
31
+ BatchEventsQueue.instance.enqueue(payload)
32
+ else
33
+ # Send the constructed payload via POST request
34
+ NetworkUtil.send_event(properties, payload)
35
+ end
36
+ end
37
+
38
+ # Sends a usage stats event to VWO.
39
+ # @param usage_stats_account_id - The account id for usage stats.
40
+ def send_sdk_usage_stats_event(usage_stats_account_id)
41
+ # create query parameters
42
+ properties = NetworkUtil.get_events_base_properties(EventEnum::USAGE_STATS, nil, nil, true, usage_stats_account_id)
43
+
44
+ # create payload
45
+ payload = NetworkUtil.get_sdk_usage_stats_payload_data(EventEnum::USAGE_STATS, usage_stats_account_id)
46
+
47
+ # check if batching is enabled
48
+ if BatchEventsQueue.instance
49
+ # add the payload to the batch events queue
50
+ BatchEventsQueue.instance.enqueue(payload)
51
+ else
52
+ # send event
53
+ NetworkUtil.send_event(properties, payload)
54
+ end
55
+ end
@@ -0,0 +1,141 @@
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 '../enums/campaign_type_enum'
16
+ require_relative '../models/campaign/campaign_model'
17
+ require_relative '../models/campaign/feature_model'
18
+ require_relative '../models/settings/settings_model'
19
+ require_relative '../utils/data_type_util'
20
+ require_relative '../services/logger_service'
21
+ require_relative '../enums/log_level_enum'
22
+ require_relative '../constants/constants'
23
+ require_relative '../utils/data_type_util'
24
+
25
+ # Clones an object deeply.
26
+ # @param obj [Object] The object to clone.
27
+ # @return [Object] The cloned object.
28
+ def clone_object(obj)
29
+ return obj unless obj
30
+ Marshal.load(Marshal.dump(obj))
31
+ end
32
+
33
+ # Gets the current time in ISO string format.
34
+ # @return [String] The current time in ISO string format.
35
+ def get_current_time
36
+ Time.now.utc.iso8601
37
+ end
38
+
39
+ # Gets the current Unix timestamp in seconds.
40
+ # @return [Integer] The current Unix timestamp.
41
+ def get_current_unix_timestamp
42
+ Time.now.to_i
43
+ end
44
+
45
+ # Gets the current Unix timestamp in milliseconds.
46
+ # @return [Integer] The current Unix timestamp in milliseconds.
47
+ def get_current_unix_timestamp_in_millis
48
+ (Time.now.to_f * 1000).to_i
49
+ end
50
+
51
+ # Generates a random number between 0 and 1.
52
+ # @return [Float] A random number.
53
+ def get_random_number
54
+ rand
55
+ end
56
+
57
+ # Retrieves specific rules based on the type from a feature.
58
+ # @param feature [FeatureModel] The feature object.
59
+ # @param type [CampaignTypeEnum, nil] The type of the rules to retrieve.
60
+ # @return [Array] An array of rules that match the type.
61
+ def get_specific_rules_based_on_type(feature, type = nil)
62
+ return [] unless feature&.get_rules_linked_campaign
63
+
64
+ return feature.get_rules_linked_campaign if type.nil? || !DataTypeUtil.is_string(type)
65
+
66
+ feature.get_rules_linked_campaign.select do |rule|
67
+ rule_model = CampaignModel.new.model_from_dictionary(rule)
68
+ rule_model.get_type == type
69
+ end
70
+ end
71
+
72
+ # Retrieves all AB and Personalize rules from a feature.
73
+ # @param feature [FeatureModel] The feature object.
74
+ # @return [Array] An array of AB and Personalize rules.
75
+ def get_all_experiment_rules(feature)
76
+ return [] unless feature
77
+
78
+ feature.get_rules_linked_campaign.select do |rule|
79
+ [CampaignTypeEnum::AB, CampaignTypeEnum::PERSONALIZE].include?(rule.get_type)
80
+ end
81
+ end
82
+
83
+ # Retrieves a feature by its key from the settings.
84
+ # @param settings [SettingsModel] The settings containing features.
85
+ # @param feature_key [String] The key of the feature to find.
86
+ # @return [FeatureModel, nil] The feature if found, otherwise nil.
87
+ def get_feature_from_key(settings, feature_key)
88
+ return nil unless settings&.get_features
89
+
90
+ settings.get_features.find { |feature| feature.get_key == feature_key }
91
+ end
92
+
93
+ # Checks if an event exists within any feature's metrics.
94
+ # @param event_name [String] The name of the event to check.
95
+ # @param settings [SettingsModel] The settings containing features.
96
+ # @return [Boolean] True if the event exists, otherwise false.
97
+ def does_event_belong_to_any_feature(event_name, settings)
98
+ settings.get_features.any? do |feature|
99
+ feature.get_metrics.any? { |metric| metric.get_identifier == event_name }
100
+ end
101
+ end
102
+
103
+ # Adds linked campaigns to each feature in the settings based on rules.
104
+ # @param settings [SettingsModel] The settings file to modify.
105
+ def add_linked_campaigns_to_settings(settings)
106
+ campaign_map = settings.get_campaigns.each_with_object({}) do |campaign, map|
107
+ map[campaign.get_id] = campaign
108
+ end
109
+
110
+ settings.get_features.each do |feature|
111
+ rules_linked_campaign = feature.get_rules.map do |rule|
112
+ campaign = campaign_map[rule.get_campaign_id]
113
+ next unless campaign
114
+
115
+ linked_campaign = campaign.clone
116
+ linked_campaign.set_rule_key(rule.get_rule_key)
117
+ if rule.get_variation_id
118
+ variation = campaign.get_variations.find { |v| v.get_id == rule.get_variation_id }
119
+ linked_campaign.set_variations([variation]) if variation
120
+ end
121
+
122
+ linked_campaign
123
+ end.compact
124
+
125
+ feature.set_rules_linked_campaign(rules_linked_campaign)
126
+ end
127
+ end
128
+
129
+ # Formats error messages for logging
130
+ # @param error [Object] The error object (can be Error, String, Hash, etc.)
131
+ # @return [String] The formatted error message
132
+ def get_formatted_error_message(error)
133
+ if error.is_a?(StandardError)
134
+ error.message
135
+ elsif error.is_a?(String)
136
+ error
137
+ elsif error.is_a?(Hash) || error.is_a?(Array)
138
+ error.to_json
139
+ end
140
+ error
141
+ end
@@ -0,0 +1,101 @@
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 '../models/settings/settings_model'
16
+ require_relative '../packages/network_layer/manager/network_manager'
17
+ require_relative '../packages/network_layer/models/request_model'
18
+ require_relative '../enums/http_method_enum'
19
+ require_relative '../services/settings_service'
20
+ require_relative '../enums/campaign_type_enum'
21
+ require_relative '../services/logger_service'
22
+ require_relative '../enums/log_level_enum'
23
+ require_relative '../enums/api_enum'
24
+
25
+ # Retrieves data from a web service using the specified query parameters and endpoint.
26
+ # @param query_params [Hash] The parameters to be used in the query string of the request.
27
+ # @param endpoint [String] The endpoint URL to which the request is sent.
28
+ # @return [Hash, Boolean] The response data or false if an error occurs.
29
+ def get_from_gateway_service(query_params, endpoint)
30
+ network_instance = NetworkManager.instance
31
+
32
+ unless SettingsService.instance.is_gateway_service_provided
33
+ LoggerService.log(LogLevelEnum::ERROR, "INVALID_GATEWAY_URL", { an: ApiEnum::GET_FLAG})
34
+ return false
35
+ end
36
+
37
+ begin
38
+ request = RequestModel.new(
39
+ SettingsService.instance.hostname,
40
+ HttpMethodEnum::GET,
41
+ SettingsService.instance.get_updated_endpoint_with_collection_prefix(endpoint, SettingsService.instance.is_gateway_service_provided),
42
+ query_params,
43
+ nil,
44
+ nil,
45
+ SettingsService.instance.protocol,
46
+ SettingsService.instance.port
47
+ )
48
+
49
+ response = network_instance.get(request)
50
+ return response.get_data
51
+ rescue StandardError => e
52
+ LoggerService.log(LogLevelEnum::ERROR, "ERROR_FETCHING_SETTINGS", { err: e.message, an: ApiEnum::GET_FLAG})
53
+ return false
54
+ end
55
+ end
56
+
57
+ # Encodes the query parameters to ensure they are URL-safe.
58
+ # @param query_params [Hash] The query parameters to be encoded.
59
+ # @return [Hash] An object containing the encoded query parameters.
60
+ def get_query_params(query_params)
61
+ encoded_params = {}
62
+
63
+ query_params.each do |key, value|
64
+ encoded_params[key] = URI.encode_www_form_component(value.to_s)
65
+ end
66
+
67
+ encoded_params
68
+ end
69
+
70
+ # Adds isGatewayServiceRequired flag to each feature in the settings based on pre-segmentation.
71
+ # @param settings [SettingsModel] The settings file to modify.
72
+ def add_is_gateway_service_required_flag(settings)
73
+ pattern = /
74
+ (?!custom_variable\s*:\s*{\s*"name"\s*:\s*") # Prevent matching inside custom_variable
75
+ \b(country|region|city|os|device|device_type|browser_string|ua|browser_version|os_version)\b
76
+ |
77
+ "custom_variable"\s*:\s*{\s*"name"\s*:\s*"inlist\([^)]*\)"
78
+ /x
79
+
80
+ settings.get_features.each do |feature|
81
+ feature.get_rules_linked_campaign.each do |rule|
82
+ segments = {}
83
+
84
+ if [CampaignTypeEnum::PERSONALIZE, CampaignTypeEnum::ROLLOUT].include?(rule.get_type)
85
+ segments = rule.get_variations[0].get_segments
86
+ else
87
+ segments = rule.get_segments
88
+ end
89
+
90
+ next unless segments
91
+
92
+ json_segments = segments.to_json
93
+ matches = json_segments.scan(pattern)
94
+
95
+ if matches.any?
96
+ feature.set_is_gateway_service_required(true)
97
+ break
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,66 @@
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 '../models/settings/settings_model'
16
+ require_relative './network_util'
17
+ require_relative '../models/user/context_model'
18
+ require_relative '../enums/event_enum'
19
+ require_relative '../services/batch_event_queue'
20
+ require_relative '../utils/campaign_util'
21
+ # Creates and sends an impression for a variation shown event.
22
+ # This function constructs the necessary properties and payload for the event
23
+ # and uses the NetworkUtil to send a POST API request.
24
+ #
25
+ # @param settings [SettingsModel] The settings model containing configuration.
26
+ # @param campaign_id [Integer] The ID of the campaign.
27
+ # @param variation_id [Integer] The ID of the variation shown to the user.
28
+ # @param context [ContextModel] The user context model containing user-specific data.
29
+ def create_and_send_impression_for_variation_shown(settings, campaign_id, variation_id, context, feature_key)
30
+ # Get base properties for the event
31
+ properties = NetworkUtil.get_events_base_properties(
32
+ EventEnum::VARIATION_SHOWN,
33
+ URI.encode_www_form_component(context.get_user_agent), # Encode user agent for URL safety
34
+ context.get_ip_address
35
+ )
36
+
37
+ # Construct payload data for tracking the user
38
+ payload = NetworkUtil.get_track_user_payload_data(
39
+ EventEnum::VARIATION_SHOWN,
40
+ campaign_id,
41
+ variation_id,
42
+ context
43
+ )
44
+
45
+ campaign_key_with_feature_name = CampaignUtil.get_campaign_key_from_campaign_id(settings, campaign_id)
46
+ variation_name = CampaignUtil.get_variation_name_from_campaign_id_and_variation_id(settings, campaign_id, variation_id)
47
+
48
+ campaign_key = ''
49
+
50
+ if feature_key == campaign_key_with_feature_name
51
+ campaign_key = Constants::IMPACT_ANALYSIS
52
+ else
53
+ campaign_key = campaign_key_with_feature_name.split("#{feature_key}_")[1]
54
+ end
55
+
56
+ campaign_type = CampaignUtil.get_campaign_type_from_campaign_id(settings, campaign_id)
57
+
58
+ # check if batching is enabled
59
+ if BatchEventsQueue.instance
60
+ # add the payload to the batch events queue
61
+ BatchEventsQueue.instance.enqueue(payload)
62
+ else
63
+ # Send the constructed payload via POST request
64
+ NetworkUtil.send_post_api_request(properties, payload, { campaign_type: campaign_type, feature_key: feature_key, campaign_key: campaign_key, variation_name: variation_name })
65
+ end
66
+ end
@@ -0,0 +1,42 @@
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
+ # Constructs a message by replacing placeholders in a template with corresponding values from a data hash.
16
+ #
17
+ # @param template [String] The message template containing placeholders in the format `{key}`.
18
+ # @param data [Hash] An object containing keys and values used to replace the placeholders in the template.
19
+ # @return [String] The constructed message with all placeholders replaced by their corresponding values from the data hash.
20
+ def build_message(template, data = {})
21
+ begin
22
+ template.gsub(/\{([0-9a-zA-Z_]+)\}/) do |match|
23
+ key = match.tr('{}', '') # Extract the key from `{key}`
24
+
25
+ # Check for escaped placeholders like `{{key}}`
26
+ if template[template.index(match) - 1] == '{' && template[template.index(match) + match.length] == '}'
27
+ key
28
+ else
29
+ value = data[key.to_sym] || data[key] # Support both string and symbol keys
30
+
31
+ # Return empty string if key is missing
32
+ next '' if value.nil?
33
+
34
+ # If value is a callable (Proc/Lambda), execute it
35
+ value.respond_to?(:call) ? value.call : value
36
+ end
37
+ end
38
+ rescue StandardError
39
+ template # Return the original template in case of an error
40
+ end
41
+ end
42
+