lib/view_component/errors.rb



module ViewComponent
  class BaseError < StandardError
    def initialize
      super(self.class::MESSAGE)
    end
  end

  class DuplicateSlotContentError < StandardError
    MESSAGE =
      "It looks like a block was provided after calling `with_content` on COMPONENT, " \
      "which means that ViewComponent doesn't know which content to use.\n\n" \
      "To fix this issue, use either `with_content` or a block."

    def initialize(klass_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
    end
  end

  class TemplateError < StandardError
    def initialize(errors)
      super(errors.join(", "))
    end
  end

  class MultipleInlineTemplatesError < BaseError
    MESSAGE = "Inline templates can only be defined once per-component."
  end

  class MissingPreviewTemplateError < StandardError
    MESSAGE =
      "A preview template for example EXAMPLE doesn't exist.\n\n" \
      "To fix this issue, create a template for the example."

    def initialize(example)
      super(MESSAGE.gsub("EXAMPLE", example))
    end
  end

  class DuplicateContentError < StandardError
    MESSAGE =
      "It looks like a block was provided after calling `with_content` on COMPONENT, " \
      "which means that ViewComponent doesn't know which content to use.\n\n" \
      "To fix this issue, use either `with_content` or a block."

    def initialize(klass_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
    end
  end

  class EmptyOrInvalidInitializerError < StandardError
    MESSAGE =
      "The COMPONENT initializer is empty or invalid. " \
      "It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
      "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
      "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."

    def initialize(klass_name, parameter)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
    end
  end

  class MissingCollectionArgumentError < StandardError
    MESSAGE =
      "The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
      "which is required to render it as a collection.\n\n" \
      "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
      "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."

    def initialize(klass_name, parameter)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
    end
  end

  class ReservedParameterError < StandardError
    MESSAGE =
      "COMPONENT initializer can't accept the parameter `PARAMETER`, as it will override a " \
      "public ViewComponent method. To fix this issue, rename the parameter."

    def initialize(klass_name, parameter)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
    end
  end

  class InvalidCollectionArgumentError < BaseError
    MESSAGE =
      "The value of the first argument passed to `with_collection` isn't a valid collection. " \
      "Make sure it responds to `to_ary`."
  end

  class ContentSlotNameError < StandardError
    MESSAGE =
      "COMPONENT declares a slot named content, which is a reserved word in ViewComponent.\n\n" \
      "Content passed to a ViewComponent as a block is captured and assigned to the `content` accessor without having to create an explicit slot.\n\n" \
      "To fix this issue, either use the `content` accessor directly or choose a different slot name."

    def initialize(klass_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
    end
  end

  class InvalidSlotDefinitionError < BaseError
    MESSAGE =
      "Invalid slot definition. Please pass a class, " \
      "string, or callable (that is proc, lambda, etc)"
  end

  class SlotPredicateNameError < StandardError
    MESSAGE =
      "COMPONENT declares a slot named SLOT_NAME, which ends with a question mark.\n\n" \
      "This isn't allowed because the ViewComponent framework already provides predicate " \
      "methods ending in `?`.\n\n" \
      "To fix this issue, choose a different name."

    def initialize(klass_name, slot_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
    end
  end

  class RedefinedSlotError < StandardError
    MESSAGE =
      "COMPONENT declares the SLOT_NAME slot multiple times.\n\n" \
      "To fix this issue, choose a different slot name."

    def initialize(klass_name, slot_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
    end
  end

  class ReservedSingularSlotNameError < StandardError
    MESSAGE =
      "COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
      "To fix this issue, choose a different name."

    def initialize(klass_name, slot_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
    end
  end

  class ReservedPluralSlotNameError < StandardError
    MESSAGE =
      "COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
      "To fix this issue, choose a different name."

    def initialize(klass_name, slot_name)
      super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
    end
  end

  class ContentAlreadySetForPolymorphicSlotError < StandardError
    MESSAGE = "Content for slot SLOT_NAME has already been provided."

    def initialize(slot_name)
      super(MESSAGE.gsub("SLOT_NAME", slot_name.to_s))
    end
  end

  class NilWithContentError < BaseError
    MESSAGE =
      "No content provided to `#with_content` for #{self}.\n\n" \
      "To fix this issue, pass a value."
  end

  class TranslateCalledBeforeRenderError < BaseError
    MESSAGE =
      "`#translate` can't be used during initialization as it depends " \
      "on the view context that only exists once a ViewComponent is passed to " \
      "the Rails render pipeline.\n\n" \
      "It's sometimes possible to fix this issue by moving code dependent on " \
      "`#translate` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
  end

  class HelpersCalledBeforeRenderError < BaseError
    MESSAGE =
      "`#helpers` can't be used during initialization as it depends " \
      "on the view context that only exists once a ViewComponent is passed to " \
      "the Rails render pipeline.\n\n" \
      "It's sometimes possible to fix this issue by moving code dependent on " \
      "`#helpers` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
  end

  class ControllerCalledBeforeRenderError < BaseError
    MESSAGE =
      "`#controller` can't be used during initialization, as it depends " \
      "on the view context that only exists once a ViewComponent is passed to " \
      "the Rails render pipeline.\n\n" \
      "It's sometimes possible to fix this issue by moving code dependent on " \
      "`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
  end

  class NoMatchingTemplatesForPreviewError < StandardError
    MESSAGE = "Found 0 matches for templates for TEMPLATE_IDENTIFIER."

    def initialize(template_identifier)
      super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
    end
  end

  class MultipleMatchingTemplatesForPreviewError < StandardError
    MESSAGE = "Found multiple templates for TEMPLATE_IDENTIFIER."

    def initialize(template_identifier)
      super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
    end
  end

  class SystemTestControllerOnlyAllowedInTestError < BaseError
    MESSAGE = "ViewComponent SystemTest controller must only be called in a test environment for security reasons."
  end

  class SystemTestControllerNefariousPathError < BaseError
    MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
  end

  class AlreadyDefinedPolymorphicSlotSetterError < StandardError
    MESSAGE =
      "A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \
      "slot setter.\n\nPlease choose a different setter name."

    def initialize(setter_method_name, setter_name)
      super(MESSAGE.gsub("SETTER_METHOD_NAME", setter_method_name.to_s).gsub("SETTER_NAME", setter_name.to_s))
    end
  end
end