shipeasy-sdk 2.3.0 → 2.4.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/lib/shipeasy/admin.rb +14 -8
  3. data/lib/shipeasy/client.rb +27 -0
  4. data/lib/shipeasy/config.rb +12 -2
  5. data/lib/shipeasy/engine.rb +123 -70
  6. data/lib/shipeasy/logging.rb +79 -0
  7. data/lib/shipeasy/sdk/version.rb +1 -1
  8. data/lib/shipeasy-sdk.rb +3 -2
  9. data/lib/shipeasy_admin/api/{alert_rules_api.rb → alerts_api.rb} +77 -17
  10. data/lib/shipeasy_admin/api/api_keys_api.rb +229 -0
  11. data/lib/shipeasy_admin/api/attributes_api.rb +1 -1
  12. data/lib/shipeasy_admin/api/configs_api.rb +78 -1
  13. data/lib/shipeasy_admin/api/connectors_api.rb +497 -0
  14. data/lib/shipeasy_admin/api/drafts_api.rb +82 -0
  15. data/lib/shipeasy_admin/api/errors_api.rb +389 -0
  16. data/lib/shipeasy_admin/api/events_api.rb +1 -1
  17. data/lib/shipeasy_admin/api/experiments_api.rb +1 -1
  18. data/lib/shipeasy_admin/api/{gates_api.rb → flags_api.rb} +27 -27
  19. data/lib/shipeasy_admin/api/{i18n_api.rb → keys_api.rb} +54 -244
  20. data/lib/shipeasy_admin/api/{killswitches_api.rb → killswitch_api.rb} +110 -33
  21. data/lib/shipeasy_admin/api/metrics_api.rb +1 -1
  22. data/lib/shipeasy_admin/api/ops_api.rb +3 -63
  23. data/lib/shipeasy_admin/api/profiles_api.rb +226 -0
  24. data/lib/shipeasy_admin/api/projects_api.rb +64 -1
  25. data/lib/shipeasy_admin/api/universes_api.rb +1 -1
  26. data/lib/shipeasy_admin/api_client.rb +1 -1
  27. data/lib/shipeasy_admin/api_error.rb +1 -1
  28. data/lib/shipeasy_admin/api_model_base.rb +1 -1
  29. data/lib/shipeasy_admin/configuration.rb +1 -1
  30. data/lib/shipeasy_admin/models/approve_event_request.rb +2 -2
  31. data/lib/shipeasy_admin/models/approve_event_response.rb +1 -1
  32. data/lib/shipeasy_admin/models/claude_trigger_config.rb +204 -0
  33. data/lib/shipeasy_admin/models/connector_event.rb +40 -0
  34. data/lib/shipeasy_admin/models/connector_provider.rb +45 -0
  35. data/lib/shipeasy_admin/models/connector_record.rb +468 -0
  36. data/lib/shipeasy_admin/models/copilot_trigger_config.rb +276 -0
  37. data/lib/shipeasy_admin/models/create_alert_rule_request.rb +6 -31
  38. data/lib/shipeasy_admin/models/create_alert_rule_response.rb +1 -1
  39. data/lib/shipeasy_admin/models/create_claude_trigger_request.rb +309 -0
  40. data/lib/shipeasy_admin/models/create_config_request.rb +14 -3
  41. data/lib/shipeasy_admin/models/create_config_response.rb +1 -1
  42. data/lib/shipeasy_admin/models/create_connector_request.rb +64 -0
  43. data/lib/shipeasy_admin/models/create_connector_response.rb +165 -0
  44. data/lib/shipeasy_admin/models/create_copilot_trigger_request.rb +316 -0
  45. data/lib/shipeasy_admin/models/create_cursor_trigger_request.rb +352 -0
  46. data/lib/shipeasy_admin/models/create_event_request.rb +2 -19
  47. data/lib/shipeasy_admin/models/create_event_response.rb +1 -1
  48. data/lib/shipeasy_admin/models/create_experiment_request.rb +39 -101
  49. data/lib/shipeasy_admin/models/create_experiment_response.rb +1 -1
  50. data/lib/shipeasy_admin/models/create_gate_request.rb +6 -43
  51. data/lib/shipeasy_admin/models/create_gate_response.rb +1 -1
  52. data/lib/shipeasy_admin/models/create_i18n_profile_request.rb +1 -1
  53. data/lib/shipeasy_admin/models/create_i18n_profile_response.rb +1 -1
  54. data/lib/shipeasy_admin/models/create_jules_trigger_request.rb +352 -0
  55. data/lib/shipeasy_admin/models/create_key_request.rb +297 -0
  56. data/lib/shipeasy_admin/models/create_key_response.rb +286 -0
  57. data/lib/shipeasy_admin/models/create_killswitch_request.rb +14 -3
  58. data/lib/shipeasy_admin/models/create_killswitch_response.rb +1 -1
  59. data/lib/shipeasy_admin/models/create_metric_request.rb +6 -18
  60. data/lib/shipeasy_admin/models/create_metric_response.rb +1 -1
  61. data/lib/shipeasy_admin/models/create_o_auth_connector_request.rb +273 -0
  62. data/lib/shipeasy_admin/models/create_ops_item_request.rb +2 -2
  63. data/lib/shipeasy_admin/models/create_ops_item_response.rb +2 -2
  64. data/lib/shipeasy_admin/models/create_universe_request.rb +6 -23
  65. data/lib/shipeasy_admin/models/create_universe_response.rb +1 -1
  66. data/lib/shipeasy_admin/models/cursor_trigger_config.rb +231 -0
  67. data/lib/shipeasy_admin/models/delete_alert_rule_response.rb +1 -1
  68. data/lib/shipeasy_admin/models/delete_config_response.rb +1 -1
  69. data/lib/shipeasy_admin/models/delete_connector_response.rb +188 -0
  70. data/lib/shipeasy_admin/models/delete_event_response.rb +1 -1
  71. data/lib/shipeasy_admin/models/delete_experiment_response.rb +1 -1
  72. data/lib/shipeasy_admin/models/delete_gate_response.rb +1 -1
  73. data/lib/shipeasy_admin/models/delete_killswitch_response.rb +1 -1
  74. data/lib/shipeasy_admin/models/delete_metric_response.rb +1 -1
  75. data/lib/shipeasy_admin/models/delete_universe_response.rb +1 -1
  76. data/lib/shipeasy_admin/models/disable_gate_response.rb +1 -1
  77. data/lib/shipeasy_admin/models/discard_config_draft_request.rb +7 -10
  78. data/lib/shipeasy_admin/models/discard_config_draft_response.rb +1 -1
  79. data/lib/shipeasy_admin/models/enable_gate_response.rb +1 -1
  80. data/lib/shipeasy_admin/models/env.rb +41 -0
  81. data/lib/shipeasy_admin/models/{error_response.rb → error.rb} +29 -7
  82. data/lib/shipeasy_admin/models/error_code.rb +52 -0
  83. data/lib/shipeasy_admin/models/error_record.rb +597 -0
  84. data/lib/shipeasy_admin/models/error_series_request.rb +251 -0
  85. data/lib/shipeasy_admin/models/error_series_response.rb +195 -0
  86. data/lib/shipeasy_admin/models/error_series_response_rows_inner.rb +192 -0
  87. data/lib/shipeasy_admin/models/{create_experiment_request_goal_metric.rb → experiment_inline_metric.rb} +9 -8
  88. data/lib/shipeasy_admin/models/file_error_ticket_response.rb +193 -0
  89. data/lib/shipeasy_admin/models/fire_connector_request.rb +149 -0
  90. data/lib/shipeasy_admin/models/fire_connector_response.rb +176 -0
  91. data/lib/shipeasy_admin/models/get_config_response.rb +1 -1
  92. data/lib/shipeasy_admin/models/get_current_project_response.rb +1 -1
  93. data/lib/shipeasy_admin/models/get_current_project_response_module_translations.rb +1 -1
  94. data/lib/shipeasy_admin/models/get_event_response.rb +1 -1
  95. data/lib/shipeasy_admin/models/get_experiment_response.rb +1 -1
  96. data/lib/shipeasy_admin/models/get_experiment_results_response.rb +1 -1
  97. data/lib/shipeasy_admin/models/get_experiment_results_response_experiment.rb +1 -1
  98. data/lib/shipeasy_admin/models/get_experiment_results_response_results_inner.rb +1 -1
  99. data/lib/shipeasy_admin/models/get_experiment_timeseries_response.rb +1 -1
  100. data/lib/shipeasy_admin/models/get_experiment_timeseries_response_experiment.rb +1 -1
  101. data/lib/shipeasy_admin/models/get_experiment_timeseries_response_series_inner.rb +1 -1
  102. data/lib/shipeasy_admin/models/get_killswitch_response.rb +1 -1
  103. data/lib/shipeasy_admin/models/get_metric_response.rb +1 -1
  104. data/lib/shipeasy_admin/models/get_ops_item_response.rb +1 -1
  105. data/lib/shipeasy_admin/models/jules_trigger_config.rb +240 -0
  106. data/lib/shipeasy_admin/models/key_record.rb +353 -0
  107. data/lib/shipeasy_admin/models/killswitch_value.rb +178 -0
  108. data/lib/shipeasy_admin/models/link_pr_to_ops_item_request.rb +1 -1
  109. data/lib/shipeasy_admin/models/link_pr_to_ops_item_response.rb +1 -1
  110. data/lib/shipeasy_admin/models/list_alert_rules_response_inner.rb +2 -2
  111. data/lib/shipeasy_admin/models/list_attributes_response_inner.rb +1 -1
  112. data/lib/shipeasy_admin/models/list_config_activity_response_inner.rb +1 -1
  113. data/lib/shipeasy_admin/models/list_configs_response.rb +1 -1
  114. data/lib/shipeasy_admin/models/list_configs_response_data_inner.rb +1 -1
  115. data/lib/shipeasy_admin/models/list_configs_response_data_inner_drafts_value.rb +1 -1
  116. data/lib/shipeasy_admin/models/list_configs_response_data_inner_envs_value.rb +1 -1
  117. data/lib/shipeasy_admin/models/list_events_response_inner.rb +1 -1
  118. data/lib/shipeasy_admin/models/list_events_response_inner_properties_inner.rb +1 -31
  119. data/lib/shipeasy_admin/models/list_experiments_response.rb +1 -1
  120. data/lib/shipeasy_admin/models/list_experiments_response_data_inner.rb +1 -1
  121. data/lib/shipeasy_admin/models/list_experiments_response_data_inner_groups_inner.rb +1 -18
  122. data/lib/shipeasy_admin/models/list_gates_response.rb +1 -1
  123. data/lib/shipeasy_admin/models/list_gates_response_data_inner.rb +1 -1
  124. data/lib/shipeasy_admin/models/list_gates_response_data_inner_enabled.rb +1 -1
  125. data/lib/shipeasy_admin/models/list_gates_response_data_inner_rules_inner.rb +1 -1
  126. data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner.rb +1 -1
  127. data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner_one_of.rb +1 -18
  128. data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner_one_of1.rb +1 -1
  129. data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner_one_of_ramp.rb +1 -1
  130. data/lib/shipeasy_admin/models/list_i18n_drafts_response_inner.rb +1 -1
  131. data/lib/shipeasy_admin/models/list_i18n_keys_response.rb +1 -1
  132. data/lib/shipeasy_admin/models/list_i18n_keys_response_keys_inner.rb +1 -1
  133. data/lib/shipeasy_admin/models/list_i18n_profiles_response_inner.rb +1 -1
  134. data/lib/shipeasy_admin/models/list_keys_response.rb +180 -0
  135. data/lib/shipeasy_admin/models/list_killswitches_response.rb +1 -1
  136. data/lib/shipeasy_admin/models/list_killswitches_response_data_inner.rb +1 -1
  137. data/lib/shipeasy_admin/models/list_killswitches_response_data_inner_envs_value.rb +1 -1
  138. data/lib/shipeasy_admin/models/list_metrics_response_inner.rb +1 -1
  139. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir.rb +3 -10
  140. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg.rb +1 -1
  141. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of.rb +1 -1
  142. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of1.rb +1 -1
  143. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of2.rb +1 -1
  144. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of3.rb +1 -1
  145. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of4.rb +1 -1
  146. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of5.rb +1 -1
  147. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of6.rb +1 -1
  148. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of7.rb +1 -1
  149. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of8.rb +1 -1
  150. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of9.rb +1 -1
  151. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of9_numerator.rb +1 -1
  152. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of9_numerator_filters_inner.rb +1 -1
  153. data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_group_by.rb +1 -1
  154. data/lib/shipeasy_admin/models/list_ops_items_response_inner.rb +1 -1
  155. data/lib/shipeasy_admin/models/list_slack_channels_response.rb +1 -1
  156. data/lib/shipeasy_admin/models/list_slack_channels_response_channels_inner.rb +1 -1
  157. data/lib/shipeasy_admin/models/list_universes_response.rb +1 -1
  158. data/lib/shipeasy_admin/models/list_universes_response_data_inner.rb +1 -1
  159. data/lib/shipeasy_admin/models/{list_alert_rules_response_inner_notify.rb → notification_target.rb} +6 -24
  160. data/lib/shipeasy_admin/models/{list_alert_rules_response_inner_notify_slack_channel.rb → notification_target_slack_channel.rb} +4 -7
  161. data/lib/shipeasy_admin/models/notify_ops_request.rb +1 -1
  162. data/lib/shipeasy_admin/models/notify_ops_response.rb +1 -1
  163. data/lib/shipeasy_admin/models/publish_config_draft_request.rb +7 -10
  164. data/lib/shipeasy_admin/models/publish_config_draft_response.rb +7 -10
  165. data/lib/shipeasy_admin/models/publish_i18n_profile_request.rb +1 -1
  166. data/lib/shipeasy_admin/models/publish_i18n_profile_response.rb +1 -1
  167. data/lib/shipeasy_admin/models/push_i18n_keys_request.rb +1 -1
  168. data/lib/shipeasy_admin/models/push_i18n_keys_request_keys_inner.rb +1 -1
  169. data/lib/shipeasy_admin/models/push_i18n_keys_response.rb +1 -1
  170. data/lib/shipeasy_admin/models/reanalyze_experiment_response.rb +1 -1
  171. data/lib/shipeasy_admin/models/revoke_key_response.rb +216 -0
  172. data/lib/shipeasy_admin/models/save_config_draft_request.rb +7 -10
  173. data/lib/shipeasy_admin/models/save_config_draft_response.rb +7 -10
  174. data/lib/shipeasy_admin/models/search_hit.rb +284 -0
  175. data/lib/shipeasy_admin/models/search_response.rb +167 -0
  176. data/lib/shipeasy_admin/models/set_experiment_metrics_request.rb +1 -1
  177. data/lib/shipeasy_admin/models/set_experiment_metrics_request_metrics_inner.rb +1 -1
  178. data/lib/shipeasy_admin/models/set_experiment_metrics_response.rb +1 -1
  179. data/lib/shipeasy_admin/models/set_experiment_metrics_response_metrics_inner.rb +1 -1
  180. data/lib/shipeasy_admin/models/set_experiment_status_request.rb +1 -1
  181. data/lib/shipeasy_admin/models/set_experiment_status_response.rb +1 -1
  182. data/lib/shipeasy_admin/models/set_i18n_label_request.rb +213 -0
  183. data/lib/shipeasy_admin/models/set_i18n_label_response.rb +472 -0
  184. data/lib/shipeasy_admin/models/set_killswitch_switch_request.rb +7 -10
  185. data/lib/shipeasy_admin/models/set_killswitch_switch_response.rb +7 -10
  186. data/lib/shipeasy_admin/models/set_killswitch_value_request.rb +214 -0
  187. data/lib/shipeasy_admin/models/set_killswitch_value_response.rb +283 -0
  188. data/lib/shipeasy_admin/models/test_connector_response.rb +189 -0
  189. data/lib/shipeasy_admin/models/unset_killswitch_switch_request.rb +7 -10
  190. data/lib/shipeasy_admin/models/unset_killswitch_switch_response.rb +7 -10
  191. data/lib/shipeasy_admin/models/update_alert_rule_request.rb +2 -4
  192. data/lib/shipeasy_admin/models/update_alert_rule_response.rb +1 -1
  193. data/lib/shipeasy_admin/models/update_config_request.rb +2 -2
  194. data/lib/shipeasy_admin/models/update_config_response.rb +1 -1
  195. data/lib/shipeasy_admin/models/update_config_schema_request.rb +168 -0
  196. data/lib/shipeasy_admin/models/update_config_schema_response.rb +165 -0
  197. data/lib/shipeasy_admin/models/update_connector_request.rb +211 -0
  198. data/lib/shipeasy_admin/models/update_connector_response.rb +166 -0
  199. data/lib/shipeasy_admin/models/update_error_status_request.rb +190 -0
  200. data/lib/shipeasy_admin/models/update_event_request.rb +2 -2
  201. data/lib/shipeasy_admin/models/update_event_response.rb +1 -1
  202. data/lib/shipeasy_admin/models/update_experiment_request.rb +5 -4
  203. data/lib/shipeasy_admin/models/update_experiment_response.rb +1 -1
  204. data/lib/shipeasy_admin/models/update_gate_request.rb +2 -2
  205. data/lib/shipeasy_admin/models/update_gate_response.rb +1 -1
  206. data/lib/shipeasy_admin/models/update_i18n_key_request.rb +1 -1
  207. data/lib/shipeasy_admin/models/update_i18n_key_response.rb +1 -1
  208. data/lib/shipeasy_admin/models/update_killswitch_request.rb +2 -2
  209. data/lib/shipeasy_admin/models/update_killswitch_response.rb +1 -1
  210. data/lib/shipeasy_admin/models/update_ops_item_request.rb +1 -1
  211. data/lib/shipeasy_admin/models/update_ops_item_response.rb +1 -1
  212. data/lib/shipeasy_admin/models/update_universe_request.rb +2 -2
  213. data/lib/shipeasy_admin/models/update_universe_response.rb +1 -1
  214. data/lib/shipeasy_admin/models/upsert_project_request.rb +1 -1
  215. data/lib/shipeasy_admin/models/upsert_project_response.rb +1 -1
  216. data/lib/shipeasy_admin/version.rb +1 -1
  217. data/lib/shipeasy_admin.rb +56 -9
  218. metadata +58 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 434e056f9f0849c73316e812f9ff8918ad838d9161283db0b3a8c9c7450b1077
4
- data.tar.gz: beb6a893abc08ec7463a64fb2ec22f7df014f4da147b824a5f7b8d70ed3f34a0
3
+ metadata.gz: 991e86824a2b5fb735a7d7f53dbff73c635f1a4adb5a3056e3b5c359a164dcbc
4
+ data.tar.gz: 897872d04e78e03dd76f26b5e82108a5a18ef7eea749cf9657ced843e63843c4
5
5
  SHA512:
6
- metadata.gz: 17339d55c4ed50599d3cc2cfa04f2f5a53e57a5478fb626bbf999ac6297da3a35a47e9a139150b4cfcce209c6e29f1b033180c9f5cc64923f106f5c1379adff8
7
- data.tar.gz: 57fb7a408683111fd2f0aee2b389c97f5de038b22b2b6bbf0ff757c193220740a766951a7cda8baa59c58fc83bc26ea7e1103a07944e9206555a22970d071b19
6
+ metadata.gz: 540e78bcb95b819ad2a74c8d69387f5ed252b9b6fc9c12ca89f0aa9b1d6ff1653197394721f35931808abb89fa0a4bc002ce7f1c152857e5e80bfd6759bf2c8e
7
+ data.tar.gz: 36a1275319ef6958380451dd186e0dcd90cbdb04110b0cdf96e59a5e013d8a7a730456672325f9445eae0c4e2ac5f9ace72c936f9d039d9575e0fbd4c5933889
@@ -37,23 +37,29 @@ module Shipeasy
37
37
  module Admin
38
38
  # Programmatic client for the Shipeasy Admin REST API. Each resource group is
39
39
  # a lazily-constructed, memoized reader whose methods map 1:1 to the OpenAPI
40
- # operations: #gates, #configs, #killswitches, #experiments, #universes,
41
- # #metrics, #events, #alert_rules, #attributes, #projects, #ops, #i18n.
40
+ # operations: #flags, #configs, #killswitch, #experiments, #universes,
41
+ # #attributes, #metrics, #events, #ops, #alerts, #projects, #profiles, #keys,
42
+ # #drafts, #errors, #connectors, #api_keys.
42
43
  class Client
43
44
  # Friendly reader name => generated Api class.
44
45
  APIS = {
45
- gates: Generated::GatesApi,
46
+ flags: Generated::FlagsApi,
46
47
  configs: Generated::ConfigsApi,
47
- killswitches: Generated::KillswitchesApi,
48
+ killswitch: Generated::KillswitchApi,
48
49
  experiments: Generated::ExperimentsApi,
49
50
  universes: Generated::UniversesApi,
51
+ attributes: Generated::AttributesApi,
50
52
  metrics: Generated::MetricsApi,
51
53
  events: Generated::EventsApi,
52
- alert_rules: Generated::AlertRulesApi,
53
- attributes: Generated::AttributesApi,
54
- projects: Generated::ProjectsApi,
55
54
  ops: Generated::OpsApi,
56
- i18n: Generated::I18nApi,
55
+ alerts: Generated::AlertsApi,
56
+ projects: Generated::ProjectsApi,
57
+ profiles: Generated::ProfilesApi,
58
+ keys: Generated::KeysApi,
59
+ drafts: Generated::DraftsApi,
60
+ errors: Generated::ErrorsApi,
61
+ connectors: Generated::ConnectorsApi,
62
+ api_keys: Generated::APIKeysApi,
57
63
  }.freeze
58
64
 
59
65
  # @param api_key [String] admin SDK key, sent as `Authorization: Bearer <api_key>`.
@@ -34,35 +34,62 @@ module Shipeasy
34
34
  @attributes = engine.bind_attributes(mapped)
35
35
  end
36
36
 
37
+ # NOTE on fail-safe reads: the engine's runtime methods already never raise
38
+ # (each is wrapped in Engine#safe_run). The extra defensive rescue here is a
39
+ # belt-and-braces guard so even an unexpected failure BEFORE the engine call
40
+ # (e.g. an @attributes deref) still returns the documented safe default
41
+ # rather than propagating into product code.
42
+
37
43
  def get_flag(name, default: false)
38
44
  @engine.get_flag(name, @attributes, default: default)
45
+ rescue StandardError => e
46
+ Shipeasy::Logging.error "[shipeasy] Client#get_flag('#{name}') failed — returning default: #{e.message}"
47
+ default
39
48
  end
40
49
 
41
50
  def get_flag_detail(name)
42
51
  @engine.get_flag_detail(name, @attributes)
52
+ rescue StandardError => e
53
+ Shipeasy::Logging.error "[shipeasy] Client#get_flag_detail('#{name}') failed — returning safe default: #{e.message}"
54
+ Shipeasy::Engine::FlagDetail.new(value: false, reason: Shipeasy::Engine::REASON_CLIENT_NOT_READY)
43
55
  end
44
56
 
45
57
  # Configs are not user-scoped, but exposed here for one-stop ergonomics.
46
58
  def get_config(name, decode = nil, default: nil)
47
59
  @engine.get_config(name, decode, default: default)
60
+ rescue StandardError => e
61
+ Shipeasy::Logging.error "[shipeasy] Client#get_config('#{name}') failed — returning default: #{e.message}"
62
+ default
48
63
  end
49
64
 
50
65
  def get_experiment(name, default_params, decode = nil)
51
66
  @engine.get_experiment(name, @attributes, default_params, decode)
67
+ rescue StandardError => e
68
+ Shipeasy::Logging.error "[shipeasy] Client#get_experiment('#{name}') failed — returning safe default: #{e.message}"
69
+ Shipeasy::SDK::Eval::ExperimentResult.new(in_experiment: false, group: "control", params: default_params)
52
70
  end
53
71
 
54
72
  # Killswitches are not user-scoped; forwarded straight to the engine.
55
73
  def get_killswitch(name, switch_key = nil)
56
74
  @engine.get_killswitch(name, switch_key)
75
+ rescue StandardError => e
76
+ Shipeasy::Logging.error "[shipeasy] Client#get_killswitch('#{name}') failed — returning false: #{e.message}"
77
+ false
57
78
  end
58
79
 
59
80
  def track(event_name, props = {})
60
81
  id = @attributes["user_id"] || @attributes["anonymous_id"]
61
82
  @engine.track(id, event_name, props)
83
+ rescue StandardError => e
84
+ Shipeasy::Logging.error "[shipeasy] Client#track('#{event_name}') failed: #{e.message}"
85
+ nil
62
86
  end
63
87
 
64
88
  def log_exposure(experiment_name)
65
89
  @engine.log_exposure(@attributes, experiment_name)
90
+ rescue StandardError => e
91
+ Shipeasy::Logging.error "[shipeasy] Client#log_exposure('#{experiment_name}') failed: #{e.message}"
92
+ nil
66
93
  end
67
94
  end
68
95
 
@@ -33,6 +33,14 @@ module Shipeasy
33
33
  attr_accessor :env, :disable_telemetry, :telemetry_url,
34
34
  :private_attributes, :sticky_store
35
35
 
36
+ # SDK-wide diagnostic verbosity for the leveled logger (Shipeasy::Logging).
37
+ # One of :silent, :error, :warn (default), :info, :debug (strings accepted
38
+ # and downcased; unknown falls back to :warn). Controls the stderr output of
39
+ # every caught-error diagnostic the SDK emits — the public runtime reads
40
+ # (get_flag/get_config/…) never raise, so this only tunes how loud they are
41
+ # about a recovered failure. Threaded into the global Engine by configure.
42
+ attr_accessor :log_level
43
+
36
44
  # Fetch lifecycle for the global engine `configure` builds:
37
45
  # - init (default true): fire a one-shot fetch fire-and-forget so the first
38
46
  # `Shipeasy::Client.new(user).get_flag(...)` resolves against real rules
@@ -70,6 +78,7 @@ module Shipeasy
70
78
  @telemetry_url = nil
71
79
  @private_attributes = nil
72
80
  @sticky_store = nil
81
+ @log_level = :warn
73
82
 
74
83
  @profile = "default"
75
84
  @default_chunk = "index"
@@ -147,6 +156,7 @@ module Shipeasy
147
156
  telemetry_url: cfg.telemetry_url,
148
157
  private_attributes: cfg.private_attributes,
149
158
  sticky_store: cfg.sticky_store,
159
+ log_level: cfg.log_level,
150
160
  )
151
161
  @engine = engine
152
162
  # Capture +engine+ in the closure (not the @engine ivar, which a concurrent
@@ -155,13 +165,13 @@ module Shipeasy
155
165
  Thread.new do
156
166
  engine.init # initial fetch + background poll thread
157
167
  rescue => e
158
- warn "[shipeasy] configure(poll) background poll failed: #{e.message}"
168
+ Shipeasy::Logging.error "[shipeasy] configure(poll) background poll failed: #{e.message}"
159
169
  end
160
170
  elsif cfg.init
161
171
  Thread.new do
162
172
  engine.init_once
163
173
  rescue => e
164
- warn "[shipeasy] configure() one-shot fetch failed: #{e.message}"
174
+ Shipeasy::Logging.error "[shipeasy] configure() one-shot fetch failed: #{e.message}"
165
175
  end
166
176
  end
167
177
  engine
@@ -3,6 +3,7 @@ require "uri"
3
3
  require "json"
4
4
  require "thread"
5
5
  require "cgi"
6
+ require_relative "logging"
6
7
  require_relative "sdk/eval"
7
8
  require_relative "sdk/telemetry"
8
9
  require_relative "sdk/anon_id"
@@ -33,7 +34,12 @@ module Shipeasy
33
34
  # /sdk/i18n/loader.js) — distinct from the edge API the blobs are fetched from.
34
35
  DEFAULT_CDN_BASE = "https://cdn.shipeasy.ai"
35
36
 
36
- def initialize(api_key:, base_url: nil, env: "prod", disable_telemetry: false, telemetry_url: nil, test_mode: false, private_attributes: nil, sticky_store: nil)
37
+ def initialize(api_key:, base_url: nil, env: "prod", disable_telemetry: false, telemetry_url: nil, test_mode: false, private_attributes: nil, sticky_store: nil, log_level: nil)
38
+ # SDK-wide diagnostic verbosity. Set the leveled logger from the passed
39
+ # level (default :warn; unknown falls back to :warn). The logger is
40
+ # module-scoped, so the last-built engine wins — mirrors the TS SDK,
41
+ # where the last configure() sets the level.
42
+ Shipeasy::Logging.set_level(log_level || Shipeasy::Logging::DEFAULT_LEVEL)
37
43
  @api_key = api_key
38
44
  @base_url = (base_url || DEFAULT_BASE_URL).chomp("/")
39
45
  # Read-env tag. Used by telemetry below and stamped onto see() error
@@ -205,6 +211,12 @@ module Shipeasy
205
211
  # Evaluate a flag and return why. Telemetry ("gate" beacon) is emitted
206
212
  # exactly once here (steps 2–5), never on the OVERRIDE short-circuit.
207
213
  def get_flag_detail(name, user)
214
+ safe_run("get_flag_detail('#{name}')", FlagDetail.new(value: false, reason: REASON_CLIENT_NOT_READY)) do
215
+ get_flag_detail_inner(name, user)
216
+ end
217
+ end
218
+
219
+ def get_flag_detail_inner(name, user)
208
220
  key = name.to_s
209
221
 
210
222
  # 1. Override short-circuits before any telemetry (mirrors get_config).
@@ -233,31 +245,51 @@ module Shipeasy
233
245
  end
234
246
 
235
247
  def get_flag(name, user, default: false)
236
- detail = get_flag_detail(name, user)
237
- if detail.reason == REASON_CLIENT_NOT_READY || detail.reason == REASON_FLAG_NOT_FOUND
238
- default
239
- else
240
- detail.value
248
+ safe_run("get_flag('#{name}')", default) do
249
+ detail = get_flag_detail(name, user)
250
+ if detail.reason == REASON_CLIENT_NOT_READY || detail.reason == REASON_FLAG_NOT_FOUND
251
+ default
252
+ else
253
+ detail.value
254
+ end
241
255
  end
242
256
  end
243
257
 
244
258
  def get_config(name, decode = nil, default: nil)
245
- key = name.to_s
246
- has_override, override = @mutex.synchronize do
247
- [@config_overrides.key?(key), @config_overrides[key]]
248
- end
249
- if has_override
250
- return decode ? decode.call(override) : override
251
- end
259
+ safe_run("get_config('#{name}')", default) do
260
+ key = name.to_s
261
+ has_override, override = @mutex.synchronize do
262
+ [@config_overrides.key?(key), @config_overrides[key]]
263
+ end
264
+ if has_override
265
+ begin
266
+ next(decode ? decode.call(override) : override)
267
+ rescue => e
268
+ Shipeasy::Logging.warn "[shipeasy] get_config('#{name}') decode failed: #{e.message}"
269
+ next default
270
+ end
271
+ end
252
272
 
253
- @telemetry.emit("config", name)
254
- entry = @mutex.synchronize { @flags_blob&.dig("configs", name) }
255
- return default unless entry
256
- value = entry["value"]
257
- decode ? decode.call(value) : value
273
+ @telemetry.emit("config", name)
274
+ entry = @mutex.synchronize { @flags_blob&.dig("configs", name) }
275
+ next default unless entry
276
+ value = entry["value"]
277
+ begin
278
+ decode ? decode.call(value) : value
279
+ rescue => e
280
+ Shipeasy::Logging.warn "[shipeasy] get_config('#{name}') decode failed: #{e.message}"
281
+ default
282
+ end
283
+ end
258
284
  end
259
285
 
260
286
  def get_experiment(name, user, default_params, decode = nil)
287
+ safe_run("get_experiment('#{name}')", Eval::ExperimentResult.new(in_experiment: false, group: "control", params: default_params)) do
288
+ get_experiment_inner(name, user, default_params, decode)
289
+ end
290
+ end
291
+
292
+ def get_experiment_inner(name, user, default_params, decode = nil)
261
293
  key = name.to_s
262
294
  override = @mutex.synchronize { @exp_overrides[key] }
263
295
  if override
@@ -287,7 +319,7 @@ module Shipeasy
287
319
  params: decode.call(result.params),
288
320
  )
289
321
  rescue => e
290
- warn "[shipeasy] get_experiment('#{name}') decode failed: #{e.message}"
322
+ Shipeasy::Logging.warn "[shipeasy] get_experiment('#{name}') decode failed: #{e.message}"
291
323
  return Eval::ExperimentResult.new(in_experiment: false, group: "control", params: default_params)
292
324
  end
293
325
  end
@@ -309,16 +341,18 @@ module Shipeasy
309
341
  # value (so an unconfigured key behaves exactly like the no-key call).
310
342
  # Unknown killswitches return false. Not user-scoped.
311
343
  def get_killswitch(name, switch_key = nil)
312
- @telemetry.emit("ks", name)
313
- ks = @mutex.synchronize { @flags_blob&.dig("killswitches", name.to_s) }
314
- return false unless ks
315
- unless switch_key.nil?
316
- switches = ks["switches"] || {}
317
- key = switch_key.to_s
318
- return Eval.enabled?(switches[key]) if switches.key?(key)
319
- # key not configured → fall through to the top-level value
344
+ safe_run("get_killswitch('#{name}')", false) do
345
+ @telemetry.emit("ks", name)
346
+ ks = @mutex.synchronize { @flags_blob&.dig("killswitches", name.to_s) }
347
+ next false unless ks
348
+ unless switch_key.nil?
349
+ switches = ks["switches"] || {}
350
+ key = switch_key.to_s
351
+ next Eval.enabled?(switches[key]) if switches.key?(key)
352
+ # key not configured → fall through to the top-level value
353
+ end
354
+ Eval.enabled?(ks["killed"])
320
355
  end
321
- Eval.enabled?(ks["killed"])
322
356
  end
323
357
 
324
358
  # Batch-evaluate every loaded gate, config and experiment for +user+ into
@@ -387,24 +421,27 @@ module Shipeasy
387
421
  end
388
422
 
389
423
  def track(user_id, event_name, props = {})
390
- return if @test_mode
391
-
392
- safe_props = strip_private(props)
393
-
394
- payload = JSON.generate({
395
- events: [{
396
- type: "metric",
397
- event_name: event_name,
398
- user_id: user_id.to_s,
399
- ts: (Time.now.to_f * 1000).to_i,
400
- **(safe_props.empty? ? {} : { properties: safe_props }),
401
- }],
402
- })
403
-
404
- Thread.new do
405
- post("/collect", payload)
406
- rescue => e
407
- warn "[shipeasy] track failed: #{e.message}"
424
+ safe_run("track('#{event_name}')", nil) do
425
+ next if @test_mode
426
+
427
+ safe_props = strip_private(props)
428
+
429
+ payload = JSON.generate({
430
+ events: [{
431
+ type: "metric",
432
+ event_name: event_name,
433
+ user_id: user_id.to_s,
434
+ ts: (Time.now.to_f * 1000).to_i,
435
+ **(safe_props.empty? ? {} : { properties: safe_props }),
436
+ }],
437
+ })
438
+
439
+ Thread.new do
440
+ post("/collect", payload)
441
+ rescue => e
442
+ Shipeasy::Logging.warn "[shipeasy] track failed: #{e.message}"
443
+ end
444
+ nil
408
445
  end
409
446
  end
410
447
 
@@ -415,27 +452,30 @@ module Shipeasy
415
452
  # string is wrapped as { "user_id" => id }); if enrolled, POSTs a single
416
453
  # exposure to /collect. No-op in test mode or when the user isn't enrolled.
417
454
  def log_exposure(user_or_user_id, experiment_name)
418
- return if @test_mode
419
-
420
- user = user_or_user_id.is_a?(Hash) ? user_or_user_id : { "user_id" => user_or_user_id.to_s }
421
- result = get_experiment(experiment_name, user, {})
422
- return unless result.in_experiment
423
-
424
- u = user.transform_keys(&:to_s)
425
- payload = JSON.generate({
426
- events: [{
427
- type: "exposure",
428
- experiment: experiment_name.to_s,
429
- group: result.group,
430
- user_id: (u["user_id"] || u["anonymous_id"]).to_s,
431
- ts: (Time.now.to_f * 1000).to_i,
432
- }],
433
- })
455
+ safe_run("log_exposure('#{experiment_name}')", nil) do
456
+ next if @test_mode
457
+
458
+ user = user_or_user_id.is_a?(Hash) ? user_or_user_id : { "user_id" => user_or_user_id.to_s }
459
+ result = get_experiment(experiment_name, user, {})
460
+ next unless result.in_experiment
461
+
462
+ u = user.transform_keys(&:to_s)
463
+ payload = JSON.generate({
464
+ events: [{
465
+ type: "exposure",
466
+ experiment: experiment_name.to_s,
467
+ group: result.group,
468
+ user_id: (u["user_id"] || u["anonymous_id"]).to_s,
469
+ ts: (Time.now.to_f * 1000).to_i,
470
+ }],
471
+ })
434
472
 
435
- Thread.new do
436
- post("/collect", payload)
437
- rescue => e
438
- warn "[shipeasy] log_exposure failed: #{e.message}"
473
+ Thread.new do
474
+ post("/collect", payload)
475
+ rescue => e
476
+ Shipeasy::Logging.warn "[shipeasy] log_exposure failed: #{e.message}"
477
+ end
478
+ nil
439
479
  end
440
480
  end
441
481
 
@@ -466,6 +506,19 @@ module Shipeasy
466
506
 
467
507
  private
468
508
 
509
+ # Last-resort guard that makes a public RUNTIME method (get_flag /
510
+ # get_config / get_experiment / get_killswitch / track / log_exposure)
511
+ # unable to raise into product code, even if an internal invariant is
512
+ # violated. Runs the block; on any StandardError it logs at :error and
513
+ # returns +fallback+ (the method's documented safe default). +label+ names
514
+ # the method for the log line.
515
+ def safe_run(label, fallback)
516
+ yield
517
+ rescue StandardError => e
518
+ Shipeasy::Logging.error "[shipeasy] #{label} failed — returning safe default: #{e.message}"
519
+ fallback
520
+ end
521
+
469
522
  # Build the wire event and fire-and-forget POST it to /collect. No-op in
470
523
  # test mode (mirrors track). Spam-guarded. Never raises into caller code.
471
524
  def dispatch_see(built)
@@ -485,10 +538,10 @@ module Shipeasy
485
538
  Thread.new do
486
539
  post("/collect", payload)
487
540
  rescue => e
488
- warn "[shipeasy] see() send failed: #{e.message}"
541
+ Shipeasy::Logging.warn "[shipeasy] see() send failed: #{e.message}"
489
542
  end
490
543
  rescue => e
491
- warn "[shipeasy] see() failed: #{e.message}"
544
+ Shipeasy::Logging.error "[shipeasy] see() failed: #{e.message}"
492
545
  end
493
546
 
494
547
  # Drop caller-marked private attributes from an outbound props bag. Handles
@@ -519,7 +572,7 @@ module Shipeasy
519
572
  begin
520
573
  listener.call
521
574
  rescue => e
522
- warn "[shipeasy] on_change listener raised: #{e.message}"
575
+ Shipeasy::Logging.warn "[shipeasy] on_change listener raised: #{e.message}"
523
576
  end
524
577
  end
525
578
  end
@@ -557,7 +610,7 @@ module Shipeasy
557
610
  begin
558
611
  fetch_all
559
612
  rescue => e
560
- warn "[shipeasy] background poll failed: #{e.message}"
613
+ Shipeasy::Logging.error "[shipeasy] background poll failed: #{e.message}"
561
614
  end
562
615
  end
563
616
  end
@@ -0,0 +1,79 @@
1
+ # Leveled logger shared by the whole gem.
2
+ #
3
+ # Every diagnostic the SDK emits from a *caught* error goes through here, so a
4
+ # single `log_level` config option (default :warn) controls the SDK's stderr
5
+ # output. The contract for the SDK's public RUNTIME methods (get_flag,
6
+ # get_config, get_experiment, get_killswitch, track, log_exposure, see, …) is
7
+ # that they NEVER raise into product code — so logging itself is best-effort
8
+ # too: a broken/throwing $stderr can never take down a flag read.
9
+ #
10
+ # Level ordering (a message at level L is emitted iff the configured level is at
11
+ # least as verbose as L):
12
+ # silent < error < warn < info < debug
13
+ # :warn (the default) therefore emits `error` + `warn` and suppresses the
14
+ # informational `info` / `debug` chatter.
15
+
16
+ module Shipeasy
17
+ module Logging
18
+ # All accepted levels, in increasing verbosity.
19
+ LEVELS = %i[silent error warn info debug].freeze
20
+
21
+ RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 }.freeze
22
+
23
+ DEFAULT_LEVEL = :warn
24
+
25
+ class << self
26
+ # Set the SDK-wide log level. Accepts a symbol or string (case-insensitive);
27
+ # an unrecognised value falls back to the default :warn so a bad config value
28
+ # can never silence a genuine error unexpectedly.
29
+ def set_level(level)
30
+ @level = normalize(level)
31
+ end
32
+
33
+ # The current SDK-wide log level (defaults to :warn until set).
34
+ def level
35
+ @level || DEFAULT_LEVEL
36
+ end
37
+
38
+ # Normalise any input to a known level symbol; unknown → DEFAULT_LEVEL.
39
+ def normalize(level)
40
+ sym =
41
+ case level
42
+ when Symbol then level.to_s.downcase.to_sym
43
+ when String then level.downcase.to_sym
44
+ else nil
45
+ end
46
+ RANK.key?(sym) ? sym : DEFAULT_LEVEL
47
+ end
48
+
49
+ def error(msg)
50
+ emit(:error, msg)
51
+ end
52
+
53
+ def warn(msg)
54
+ emit(:warn, msg)
55
+ end
56
+
57
+ def info(msg)
58
+ emit(:info, msg)
59
+ end
60
+
61
+ def debug(msg)
62
+ emit(:debug, msg)
63
+ end
64
+
65
+ private
66
+
67
+ # Gate on the configured level, then write to $stderr exactly as the old
68
+ # bare `warn "[shipeasy] …"` calls did (Kernel#warn → $stderr). Never
69
+ # raises: a broken stream is swallowed.
70
+ def emit(msg_level, msg)
71
+ return if RANK[level] < RANK[msg_level]
72
+
73
+ Kernel.warn(msg)
74
+ rescue StandardError
75
+ # logging must never raise into product code
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,5 +1,5 @@
1
1
  module Shipeasy
2
2
  module SDK
3
- VERSION = "2.3.0"
3
+ VERSION = "2.4.0"
4
4
  end
5
5
  end
data/lib/shipeasy-sdk.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_relative "shipeasy/sdk/version"
2
+ require_relative "shipeasy/logging"
2
3
  require_relative "shipeasy/config"
3
4
  require_relative "shipeasy/sdk/murmur3"
4
5
  require_relative "shipeasy/sdk/eval"
@@ -54,7 +55,7 @@ module Shipeasy
54
55
  def self.see(problem)
55
56
  client = default_client
56
57
  if client.nil?
57
- warn "[shipeasy] see() called before a client was created — error dropped"
58
+ Shipeasy::Logging.warn "[shipeasy] see() called before a client was created — error dropped"
58
59
  return See::NullChain.new
59
60
  end
60
61
  client.see(problem)
@@ -64,7 +65,7 @@ module Shipeasy
64
65
  def self.see_violation(name)
65
66
  client = default_client
66
67
  if client.nil?
67
- warn "[shipeasy] see_violation() called before a client was created — error dropped"
68
+ Shipeasy::Logging.warn "[shipeasy] see_violation() called before a client was created — error dropped"
68
69
  return See::NullChain.new
69
70
  end
70
71
  client.see_violation(name)