instructure/canvas-lms · error · Common::InvalidResultError

Result format is invalid: #

Error message

Result format is invalid: #{content_type}

What it means

PageViews::FetchResultService#determine_result_format raises Common::InvalidResultError when the Content-Type returned by the async page-views results endpoint does not match an entry in PageViews::Common::CONTENT_TYPE_MAPPINGS (only 'application/jsonl' and 'text/csv' are supported). The service only knows how to hand back csv or jsonl download payloads, so any other MIME type means the upstream service returned something unexpected and the result cannot be interpreted safely.

Solutions

  1. Log response.header['Content-Type'] and the query_id, then inspect the actual response body to see what the upstream service really returned
  2. Verify the PageViews configuration URI points at the real async page-views API, not a proxy, SSO page, or wrong environment
  3. Confirm the query completed successfully (poll status == 'finished') before fetching results; a failed/running query can return a non-file body
  4. If the upstream legitimately added a new format, add it to PageViews::Common::CONTENT_TYPE_MAPPINGS in app/services/page_views/common.rb and handle the new format downstream

Example fix

# before
format = FetchResultService.new(config, requestor_user: user).call(query_id) # raises on unexpected Content-Type

# after
result = FetchResultService.new(config, requestor_user: user).call(query_id)
rescue PageViews::Common::InvalidResultError => e
  Rails.logger.warn("page_views result format rejected (#{e.message}), query=#{query_id}")
  redirect_to page_views_query_path(query_id), alert: t("result_unavailable")
Defensive patterns

Strategy: try-catch

Validate before calling

# guard before consuming the result
result = FetchResultService.new(config, requestor_user: user)
rescue PageViews::Common::InvalidResultError
  # fall back to re-polling status or surfacing an error
end

Type guard

def supported_content_type?(response)
  ct = response.header["Content-Type"].to_s.split(";").first.to_s.strip
  PageViews::Common::CONTENT_TYPE_MAPPINGS.key?(ct)
end

Try / catch

begin
  result = service.call(query_id)
rescue PageViews::Common::InvalidResultError => e
  Rails.logger.warn("unexpected page_views content-type: #{e.message}")
  render json: { error: "result_format_unavailable" }, status: :bad_gateway
end

Prevention

When it happens

Trigger: Calling PageViews::FetchResultService#call(query_id) after a query finishes and the results response has a Content-Type other than application/jsonl or text/csv (after stripping '; parameters'), e.g. text/html from an auth/SSO redirect page, application/json error payload, or text/plain.

Common situations: The page-views service URL points at a proxy/login page instead of the API (misconfigured base URI); the query failed upstream and an HTML/JSON error body was returned with 200; the upstream service changed or added a new export format (e.g. application/parquet) not yet mapped in CONTENT_TYPE_MAPPINGS.

Related errors


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

Appendix: source

Thrown at app/services/page_views/fetch_result_service.rb:44

        request_headers
      ) do |response|
        handle_generic_errors(response) unless response.code.to_i == 200
        response.decode_content = false # Prevent automatic decompression
        format = determine_result_format(response)
        filename = determine_filename(response).delete_suffix(".gz")
        compressed = response_compressed?(response)
        return Common::DownloadableResult.new(format:, filename:, content: response.body, compressed?: compressed)
      end
    end

    private

    def determine_result_format(response)
      raise InvalidResultError, "Missing Content-Type header in response." unless response.header["Content-Type"]

      # strip any parameters (encoding for example) from the Content-Type
      content_type = response.header["Content-Type"].split(";").first.strip
      raise Common::InvalidResultError, "Result format is invalid: #{content_type}" unless Common::CONTENT_TYPE_MAPPINGS[content_type]

      Common::CONTENT_TYPE_MAPPINGS[content_type]
    end

    def determine_filename(response)
      content_disposition = response.header["Content-Disposition"]
      if content_disposition && content_disposition =~ /filename="?([^";]+)"?/
        Regexp.last_match(1)
      else
        raise Common::InvalidResultError, "Unable to determine filename from Content-Disposition header"
      end
    end

    def response_compressed?(response)
      (response.header["Content-Encoding"] && response.header["Content-Encoding"] == "gzip") ||
        determine_filename(response).end_with?(".gz")
    end
  end

View on GitHub (pinned to 1c9f0bb801)