lib/splitclient-rb/cache/stores/localhost_split_store.rb



# frozen_string_literal: true

module SplitIoClient
  module Cache
    module Stores
      class LocalhostSplitStore
        require 'yaml'
        attr_reader :splits_repository

        def initialize(splits_repository, config, status_manager = nil)
          @splits_repository = splits_repository
          @config = config
          @status_manager = status_manager
        end

        def call
          if ENV['SPLITCLIENT_ENV'] == 'test'
            store_splits
          else
            splits_thread

            if defined?(PhusionPassenger)
              PhusionPassenger.on_event(:starting_worker_process) do |forked|
                splits_thread if forked
              end
            end
          end
        end

        private

        def splits_thread
          @config.threads[:split_store] = Thread.new do
            @config.logger.info('Starting feature flags fetcher service')
            loop do
              store_splits

              sleep(StoreUtils.random_interval(@config.features_refresh_rate))
            end
          end
        end

        def store_splits
          load_features.each do |split|
            store_split(split)
          end

          @status_manager.ready! if @status_manager
        rescue StandardError => e
          @config.logger.error('Error while parsing the split file. ' \
            'Check that the input file matches the expected format')
          @config.log_found_exception(__method__.to_s, e)
        end

        def store_split(split)
          @config.logger.debug("storing feature flag (#{split[:name]})") if @config.debug_enabled

          @splits_repository.update([split], [], -1)
        end

        def load_features
          yaml_extensions = ['.yml', '.yaml']
          if yaml_extensions.include? File.extname(@config.split_file)
            parse_yaml_features
          else
            @config.logger.warn('Localhost mode: .split mocks ' \
              'will be deprecated soon in favor of YAML files, which provide more ' \
              'targeting power. Take a look in our documentation.')

            parse_plain_text_features
          end
        end

        def parse_plain_text_features
          splits = File.open(@config.split_file).each_with_object({}) do |line, memo|
            feature, treatment = line.strip.split(' ')

            next if line.start_with?('#') || line.strip.empty?

            memo[feature] = [{ treatment: treatment }]
          end

          LocalhostSplitBuilder.build_splits(splits)
        end

        def parse_yaml_features
          splits = YAML.safe_load(File.read(@config.split_file)).each_with_object({}) do |feature, memo|
            feat_symbolized_keys = symbolize_feat_keys(feature)

            feat_name = feature.keys.first

            if memo[feat_name]
              memo[feat_name] << feat_symbolized_keys
            else
              memo[feat_name] = [feat_symbolized_keys]
            end
          end

          LocalhostSplitBuilder.build_splits(splits)
        end

        def symbolize_feat_keys(yaml_feature)
          yaml_feature.values.first.each_with_object({}) do |(k, v), memo|
            memo[k.to_sym] = k == 'config' ? v.to_json : v
          end
        end
      end
    end
  end
end