instructure/canvas-lms · error · RruleValidationError

COUNT must be > 0

Error message

COUNT must be > 0

What it means

When the RRULE includes a COUNT clause, rrule_validate_common_opts requires it to be a positive integer. The check `rropts["COUNT"].to_i > 0` fails when COUNT is 0, negative, or a non-numeric string (which .to_i turns into 0), so RruleValidationError is raised.

Solutions

  1. Ensure COUNT is a positive integer string (e.g. COUNT=5) in the RRULE
  2. If COUNT is optional in your flow, use UNTIL instead of emitting an invalid COUNT
  3. Validate the recurrence input at the API boundary before calling RruleHelper

Example fix

// before
"FREQ=DAILY;INTERVAL=1;COUNT=0"
// after
"FREQ=DAILY;INTERVAL=1;COUNT=10"
Defensive patterns

Strategy: validation

Validate before calling

count = rropts["COUNT"].to_i
raise ArgumentError, "COUNT must be a positive integer" unless rropts.key?("COUNT") && count > 0

Try / catch

begin
  RruleHelper.rrule_to_natural_language(rropts)
rescue RruleValidationError => e
  render json: { errors: [e.message] }, status: :bad_request
end

Prevention

When it happens

Trigger: Calling RruleHelper.rrule_to_natural_language with rropts containing "COUNT" => "0", "COUNT" => "-5", "COUNT" => "abc" (non-numeric), or an empty string — any value whose to_i is not > 0.

Common situations: Client sends COUNT=0 thinking it means 'unlimited'; a UI defaults the count field to 0 when the user never filled it in; string parsing of the RRULE produces an empty or malformed COUNT value; off-by-one bugs generating counts from an empty occurrence list.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/ef5e259f67f99efb. Report an issue: GitHub.

Appendix: source

Thrown at app/helpers/rrule_helper.rb:64

  rescue => e
    logger.error "RRULE to natural language failure: #{e}"
    nil
  end

  def rrule_parse(rrule)
    Hash[*rrule.sub(/^RRULE:/, "").split(/[;=]/)]
  end

  def rrule_validate_common_opts(rropts)
    raise RruleValidationError, I18n.t("Missing INTERVAL") unless rropts.key?("INTERVAL")
    raise RruleValidationError, I18n.t("INTERVAL must be > 0") unless rropts["INTERVAL"].to_i > 0

    # We do not support never ending series because each event in the series
    # must get created in the db to support the paginated calendar_events api
    raise RruleValidationError, I18n.t("Missing COUNT or UNTIL") unless rropts.key?("COUNT") || rropts.key?("UNTIL")

    if rropts.key?("COUNT")
      raise RruleValidationError, I18n.t("COUNT must be > 0") unless rropts["COUNT"].to_i > 0
      raise RruleValidationError, I18n.t("COUNT must be <= %{limit}", limit: RruleHelper::RECURRING_EVENT_LIMIT) unless rropts["COUNT"].to_i <= RruleHelper::RECURRING_EVENT_LIMIT
    else
      begin
        format_date(rropts["UNTIL"])
      rescue
        raise RruleValidationError, I18n.t("Invalid UNTIL '%{until_date}'", until_date: rropts["UNTIL"])
      end
    end
  end

  private

  DAYS_OF_WEEK = {
    "SU" => I18n.t("Sun"),
    "MO" => I18n.t("Mon"),
    "TU" => I18n.t("Tue"),
    "WE" => I18n.t("Wed"),
    "TH" => I18n.t("Thu"),

View on GitHub (pinned to 1c9f0bb801)