Skip to content

Commit 9abdd64

Browse files
feat: Add experimental plugin support
- Add Plugin interface and metadata classes following plugin specs - Extend Config class to accept plugins parameter - Modify LDClient to register plugins and collect their hooks - Add comprehensive tests for plugin functionality - Follow existing Ruby SDK patterns and conventions Co-Authored-By: [email protected] <[email protected]>
1 parent 3870181 commit 9abdd64

File tree

5 files changed

+383
-1
lines changed

5 files changed

+383
-1
lines changed

lib/ldclient-rb/config.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Config
4545
# @option opts [String] :payload_filter_key See {#payload_filter_key}
4646
# @option opts [Boolean] :omit_anonymous_contexts See {#omit_anonymous_contexts}
4747
# @option hooks [Array<Interfaces::Hooks::Hook]
48+
# @option plugins [Array<Interfaces::Plugins::Plugin]
4849
#
4950
def initialize(opts = {})
5051
@base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
@@ -79,6 +80,7 @@ def initialize(opts = {})
7980
@application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
8081
@payload_filter_key = LaunchDarkly::Impl::Util.validate_payload_filter_key(opts[:payload_filter_key] , @logger)
8182
@hooks = (opts[:hooks] || []).keep_if { |hook| hook.is_a? Interfaces::Hooks::Hook }
83+
@plugins = (opts[:plugins] || []).keep_if { |plugin| plugin.is_a? Interfaces::Plugins::Plugin }
8284
@omit_anonymous_contexts = opts.has_key?(:omit_anonymous_contexts) && opts[:omit_anonymous_contexts]
8385
@data_source_update_sink = nil
8486
@instance_id = nil
@@ -412,6 +414,14 @@ def diagnostic_opt_out?
412414
#
413415
attr_reader :hooks
414416

417+
#
418+
# Initial set of plugins for the client.
419+
#
420+
# Plugins provide an interface which allows for initialization, access to credentials, and hook registration
421+
# in a single interface.
422+
#
423+
attr_reader :plugins
424+
415425
#
416426
# Sets whether anonymous contexts should be omitted from index and identify events.
417427
#

lib/ldclient-rb/interfaces.rb

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,5 +970,138 @@ def initialize(key, context, default_value, method)
970970
end
971971
end
972972
end
973+
974+
module Plugins
975+
#
976+
# Metadata about the SDK.
977+
#
978+
class SdkMetadata
979+
# The id of the SDK (e.g., "ruby-server-sdk")
980+
# @return [String]
981+
attr_reader :name
982+
983+
# The version of the SDK
984+
# @return [String]
985+
attr_reader :version
986+
987+
# The wrapper name if this SDK is a wrapper
988+
# @return [String, nil]
989+
attr_reader :wrapper_name
990+
991+
# The wrapper version if this SDK is a wrapper
992+
# @return [String, nil]
993+
attr_reader :wrapper_version
994+
995+
def initialize(name:, version:, wrapper_name: nil, wrapper_version: nil)
996+
@name = name
997+
@version = version
998+
@wrapper_name = wrapper_name
999+
@wrapper_version = wrapper_version
1000+
end
1001+
end
1002+
1003+
#
1004+
# Metadata about the application using the SDK.
1005+
#
1006+
class ApplicationMetadata
1007+
# The id of the application
1008+
# @return [String, nil]
1009+
attr_reader :id
1010+
1011+
# The version of the application
1012+
# @return [String, nil]
1013+
attr_reader :version
1014+
1015+
def initialize(id: nil, version: nil)
1016+
@id = id
1017+
@version = version
1018+
end
1019+
end
1020+
1021+
#
1022+
# Metadata about the environment in which the SDK is running.
1023+
#
1024+
class EnvironmentMetadata
1025+
# Information about the SDK
1026+
# @return [SdkMetadata]
1027+
attr_reader :sdk
1028+
1029+
# Information about the application
1030+
# @return [ApplicationMetadata, nil]
1031+
attr_reader :application
1032+
1033+
# The SDK key used to initialize the SDK
1034+
# @return [String, nil]
1035+
attr_reader :sdk_key
1036+
1037+
def initialize(sdk:, application: nil, sdk_key: nil)
1038+
@sdk = sdk
1039+
@application = application
1040+
@sdk_key = sdk_key
1041+
end
1042+
end
1043+
1044+
#
1045+
# Metadata about a plugin implementation.
1046+
#
1047+
class PluginMetadata
1048+
# A name representing the plugin instance
1049+
# @return [String]
1050+
attr_reader :name
1051+
1052+
def initialize(name)
1053+
@name = name
1054+
end
1055+
end
1056+
1057+
#
1058+
# Mixin for extending SDK functionality via plugins.
1059+
#
1060+
# All provided plugin implementations **MUST** include this mixin. Plugins without this mixin will be ignored.
1061+
#
1062+
# This mixin includes default implementations for optional methods. This allows LaunchDarkly to expand the list
1063+
# of plugin methods without breaking customer integrations.
1064+
#
1065+
# Plugins provide an interface which allows for initialization, access to credentials, and hook registration
1066+
# in a single interface.
1067+
#
1068+
module Plugin
1069+
#
1070+
# Get metadata about the plugin implementation.
1071+
#
1072+
# @return [PluginMetadata]
1073+
#
1074+
def metadata
1075+
PluginMetadata.new('UNDEFINED')
1076+
end
1077+
1078+
#
1079+
# Register the plugin with the SDK client.
1080+
#
1081+
# This method is called during SDK initialization to allow the plugin to set up any necessary integrations,
1082+
# register hooks, or perform other initialization tasks.
1083+
#
1084+
# @param client [LDClient] The LDClient instance
1085+
# @param environment_metadata [EnvironmentMetadata] Metadata about the environment in which the SDK is running
1086+
# @return [void]
1087+
#
1088+
def register(client, environment_metadata)
1089+
# Default implementation does nothing
1090+
end
1091+
1092+
#
1093+
# Get a list of hooks that this plugin provides.
1094+
#
1095+
# This method is called before register() to collect all hooks from plugins. The hooks returned will be
1096+
# added to the SDK's hook configuration.
1097+
#
1098+
# @param environment_metadata [EnvironmentMetadata] Metadata about the environment in which the SDK is running
1099+
# @return [Array<Interfaces::Hooks::Hook>] A list of hooks to be registered with the SDK
1100+
#
1101+
def get_hooks(environment_metadata)
1102+
[]
1103+
end
1104+
end
1105+
end
9731106
end
9741107
end

lib/ldclient-rb/ldclient.rb

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ def postfork(wait_for_sec = 5)
8989
end
9090

9191
private def start_up(wait_for_sec)
92-
@hooks = Concurrent::Array.new(@config.hooks)
92+
environment_metadata = get_environment_metadata
93+
plugin_hooks = get_plugin_hooks(environment_metadata)
94+
95+
@hooks = Concurrent::Array.new(@config.hooks + plugin_hooks)
9396

9497
@shared_executor = Concurrent::SingleThreadExecutor.new
9598

@@ -156,6 +159,8 @@ def postfork(wait_for_sec = 5)
156159
@data_source = data_source_or_factory
157160
end
158161

162+
register_plugins(environment_metadata)
163+
159164
ready = @data_source.start
160165

161166
return unless wait_for_sec > 0
@@ -172,6 +177,51 @@ def postfork(wait_for_sec = 5)
172177
end
173178
end
174179

180+
private def get_environment_metadata
181+
sdk_metadata = Interfaces::Plugins::SdkMetadata.new(
182+
name: "ruby-server-sdk",
183+
version: LaunchDarkly::VERSION,
184+
wrapper_name: @config.wrapper_name,
185+
wrapper_version: @config.wrapper_version
186+
)
187+
188+
application_metadata = nil
189+
if @config.application && (!@config.application.empty?)
190+
application_metadata = Interfaces::Plugins::ApplicationMetadata.new(
191+
id: @config.application[:id],
192+
version: @config.application[:version]
193+
)
194+
end
195+
196+
Interfaces::Plugins::EnvironmentMetadata.new(
197+
sdk: sdk_metadata,
198+
application: application_metadata,
199+
sdk_key: @sdk_key
200+
)
201+
end
202+
203+
private def get_plugin_hooks(environment_metadata)
204+
hooks = []
205+
@config.plugins.each do |plugin|
206+
begin
207+
hooks.concat(plugin.get_hooks(environment_metadata))
208+
rescue => e
209+
@config.logger.error { "[LDClient] Error getting hooks from plugin #{plugin.metadata.name}: #{e}" }
210+
end
211+
end
212+
hooks
213+
end
214+
215+
private def register_plugins(environment_metadata)
216+
@config.plugins.each do |plugin|
217+
begin
218+
plugin.register(self, environment_metadata)
219+
rescue => e
220+
@config.logger.error { "[LDClient] Error registering plugin #{plugin.metadata.name}: #{e}" }
221+
end
222+
end
223+
end
224+
175225
#
176226
# Add a hook to the client. In order to register a hook before the client starts, please use the `hooks` property of
177227
# {#LDConfig}.

0 commit comments

Comments
 (0)