module SplitIoClient class LocalhostSplitClient include SplitIoClient::LocalhostUtils # # variables to if the sdk is being used in localhost mode and store the list of features attr_reader :localhost_mode attr_reader :localhost_mode_features # # Creates a new split client instance that reads from the given splits file # # @param splits_file [File] file that contains some splits # # @return [LocalhostSplitIoClient] split.io localhost client instance def initialize(splits_file, reload_rate = nil) @localhost_mode = true @localhost_mode_features = [] load_localhost_mode_features(splits_file, reload_rate) end # # method that returns the sdk gem version # # @return [string] version value for this sdk def self.sdk_version 'ruby-'+SplitIoClient::VERSION end # # obtains the treatments and configs for a given set of features # # @param key [string] evaluation key, only used with yaml split files # @param split_names [array] name of the features being validated # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls # # @return [hash] map of treatments (split_name, treatment) def get_treatments_with_config(key, split_names, attributes = nil) get_localhost_treatments(key, split_names, attributes, 'get_treatments_with_config') end # # obtains the treatments for a given set of features # # @param key [string] evaluation key, only used with yaml split files # @param split_names [array] name of the features being validated # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls # # @return [hash] map of treatments (split_name, treatment_name) def get_treatments(key, split_names, attributes = nil) treatments = get_localhost_treatments(key, split_names, attributes) return treatments if treatments.nil? keys = treatments.keys treats = treatments.map { |_,t| t[:treatment] } Hash[keys.zip(treats)] end # # obtains the treatment for a given feature # # @param key [string] evaluation key, only used with yaml split files # @param split_name [string] name of the feature that is being validated # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls # # @return [string] corresponding treatment def get_treatment(key, split_name, attributes = nil) get_localhost_treatment(key, split_name, attributes)[:treatment] end # # obtains the treatment and config for a given feature # # @param key [string] evaluation key, only used with yaml split files # @param split_name [string] name of the feature that is being validated # @param attributes [hash] kept for consistency with actual SDK client. Omitted in calls # # @return [hash] corresponding treatment and config def get_treatment_with_config(key, split_name, attributes = nil) get_localhost_treatment(key, split_name, attributes, 'get_treatment_with_config') end def track end private # # method to check if the sdk is running in localhost mode based on api key # # @return [boolean] True if is in localhost mode, false otherwise def is_localhost_mode? true end # @param key [string] evaluation key, only used with yaml split files # @param split_name [string] name of the feature that is being validated # # @return [Hash] corresponding treatment and config, control otherwise def get_localhost_treatment(key, split_name, attributes, calling_method = 'get_treatment') control_treatment = { label: Engine::Models::Label::EXCEPTION, treatment: SplitIoClient::Engine::Models::Treatment::CONTROL, config: nil } parsed_control_treatment = parsed_treatment(control_treatment) bucketing_key, matching_key = keys_from_key(key) return parsed_control_treatment unless SplitIoClient::Validators.valid_get_treatment_parameters(calling_method, key, split_name, matching_key, bucketing_key, attributes) sanitized_split_name = split_name.to_s.strip if split_name.to_s != sanitized_split_name SplitIoClient.configuration.logger.warn("get_treatment: split_name #{split_name} has extra whitespace, trimming") split_name = sanitized_split_name end treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && has_key(h[:keys], key) }.last if treatment.nil? treatment = @localhost_mode_features.select { |h| h[:feature] == split_name && h[:keys] == nil }.last end if treatment && treatment[:treatment] { treatment: treatment[:treatment], config: treatment[:config] } else parsed_control_treatment end end def get_localhost_treatments(key, split_names, attributes = nil, calling_method = 'get_treatments') return nil unless SplitIoClient::Validators.valid_get_treatments_parameters(calling_method, split_names) sanitized_split_names = sanitize_split_names(calling_method, split_names) if sanitized_split_names.empty? SplitIoClient.configuration.logger.error("#{calling_method}: split_names must be a non-empty Array") return {} end split_names.each_with_object({}) do |split_name, memo| memo.merge!(split_name => get_treatment_with_config(key, split_name, attributes)) end end def sanitize_split_names(calling_method, split_names) split_names.compact.uniq.select do |split_name| if (split_name.is_a?(String) || split_name.is_a?(Symbol)) && !split_name.empty? true elsif split_name.is_a?(String) && split_name.empty? SplitIoClient.configuration.logger.warn("#{calling_method}: you passed an empty split_name, split_name must be a non-empty String or a Symbol") false else SplitIoClient.configuration.logger.warn("#{calling_method}: you passed an invalid split_name, split_name must be a non-empty String or a Symbol") false end end end def parsed_treatment(treatment_data) { treatment: treatment_data[:treatment], config: treatment_data[:config] } end def has_key(keys, key) case keys when Array then keys.include? key when String then keys == key else false end end def keys_from_key(key) case key when Hash key.values_at(:bucketing_key, :matching_key).map { |k| k.nil? ? nil : k } else [nil, key].map { |k| k.nil? ? nil : k } end end end end