app/lib/ariadne/fetch_or_fallback_helper.rb



# frozen_string_literal: true

# Ariadne::FetchOrFallbackHelper
# A little helper to enable graceful fallbacks
#
# Use this helper to quietly ensure a value is
# one that you expect:
#
# allowed_values    - allowed options for *value*
# given_value       - input being coerced
# fallback          - returned if *given_value* is not included in *allowed_values*
#
# fetch_or_raise([1,2,3], 5) => 2
# fetch_or_raise([1,2,3], 1) => 1
# fetch_or_raise([1,2,3], nil) => 2
module Ariadne
  # :nodoc:
  module FetchOrFallbackHelper
    include LoggerHelper

    mattr_accessor :fallback_raises, default: true

    InvalidValueError = Class.new(StandardError)

    TRUE_OR_FALSE = Set.new([true, false]).freeze

    INTEGER_TYPES = Set.new(["Integer"]).freeze

    def fetch_or_fallback(allowed_values, given_value, fallback = nil, deprecated_values: nil)
      if allowed_values.include?(given_value)
        given_value
      elsif deprecated_values&.include?(given_value)
        ::Ariadne::ViewComponents.deprecation.warn("#{given_value} is deprecated and will be removed in a future version.") unless Rails.env.production? || silence_deprecations?

        given_value
      else
        if fallback_raises && ENV["RAILS_ENV"] != "production"
          raise InvalidValueError, <<~MSG
            fetch_or_fallback was called with an invalid value.

            Expected one of: #{allowed_values.inspect}
            Got: #{given_value.inspect}

            This will not raise in production, but will instead fallback to: #{fallback.inspect}
          MSG
        end

        fallback
      end
    end

    def fetch_or_raise(allowed_values, given_value, against: nil)
      if !allowed_values.is_a?(Array) && !allowed_values.is_a?(Set)
        raise ArgumentError, "allowed_values must be an array or a set; it was #{allowed_values.class}"
      end

      check_against_given_value = against || given_value
      if allowed_values.include?(check_against_given_value)
        given_value
      elsif fallback_raises
        raise InvalidValueError, <<~MSG
          fetch_or_raise was called with an invalid value.

          Expected one of: #{allowed_values.inspect}
          Got: #{given_value.inspect}
        MSG
      end
    end

    # TODO: use these two more
    def fetch_or_raise_boolean(given_value)
      fetch_or_raise(TRUE_OR_FALSE, given_value)
    end

    def fetch_or_raise_integer(given_value)
      fetch_or_raise(INTEGER_TYPES, given_value, against: given_value.class.name)
    end

    # TODO: test this
    def check_incoming_tag(preferred_tag, given_tag)
      return preferred_tag if given_tag.blank? || preferred_tag == given_tag

      unless silence_warnings?
        message = <<~MSG
          Ariadne: note that `#{preferred_tag}` is the preferred tag for `#{self.class.name}`;
          you passed `#{given_tag}` (which will still be used)
        MSG

        logger.warn(message)
      end

      given_tag
    end

    # TODO: test this
    def check_incoming_attribute(preferred_attribute, given_attribute)
      return preferred_attribute if given_attribute.blank? || preferred_attribute != given_attribute

      unless silence_warnings?
        message = <<~MSG
          Ariadne: note that `#{preferred_attribute}` is the preferred attribute for `#{self.class.name}`;
          you passed `#{given_attribute}` (which will still be used)
        MSG

        logger.warn(message)
      end

      given_attribute
    end

    # TODO: test this
    def check_incoming_value(preferred_value, given_pair)
      return preferred_value if given_pair.blank? || !given_pair.is_a?(Hash)

      given_key = given_pair.keys.first
      given_value = given_pair.values.first

      return preferred_value if given_value.blank?

      unless silence_warnings?

        message = <<~MSG
          Ariadne: note that `#{preferred_value}` is the preferred value for `#{given_key}` here;
          you passed `#{given_value}` (which will still be used)
        MSG

        logger.warn(message)
      end

      given_value
    end
  end
end