AshReports.Renderer behaviour (ash_reports v0.1.0)

Enhanced behaviour for report renderers with context management and layout support.

This module defines the core renderer behaviour for the Phase 3.1 Renderer Interface, providing a comprehensive API for format-specific rendering with advanced features including context management, layout calculation, and streaming support.

Each output format (HTML, PDF, HEEX, JSON) implements this behaviour to provide format-specific rendering capabilities while benefiting from the unified context and layout management system.

Enhanced Features (Phase 3.1)

  • Context-Aware Rendering: Renderers receive RenderContext with complete state
  • Layout Integration: Automatic layout calculation and element positioning
  • Streaming Support: Built-in support for large dataset streaming
  • Error Recovery: Comprehensive error handling and recovery mechanisms
  • Performance Monitoring: Built-in performance tracking and optimization

Renderer Implementation

Format-specific renderers must implement the enhanced callback interface:

defmodule MyReport.Html do
  @behaviour AshReports.Renderer

  def render_with_context(context, opts) do
    # Render using context
  end

  def supports_streaming?(), do: true
  def file_extension(), do: "html"
  def content_type(), do: "text/html"
end

Integration with Phase 2

Renderers seamlessly integrate with Phase 2 DataLoader results through RenderContext:

{:ok, data_result} = DataLoader.load_report(domain, :sales_report, params)
context = RenderContext.new(report, data_result, config)
{:ok, output} = Renderer.render_with_context(renderer, context)

Summary

Callbacks

Cleans up after rendering operations.

The MIME content type for this format.

The file extension for this format.

Prepares the renderer for rendering operations.

Legacy render callback for backward compatibility.

Enhanced render callback with context support.

Whether this renderer supports streaming output.

Validates that the renderer can handle the given context.

Functions

Creates a RenderContext from a report and DataLoader result.

Gets available renderers for a report module.

Gets renderer information for a specific format.

Renders a report using the appropriate renderer for the given format.

Renders a report using the enhanced context-aware API.

Checks if a renderer supports the given format.

Validates a renderer module implements the required callbacks.

Types

data()

@type data() :: any()

opts()

@type opts() :: Keyword.t()

render_result()

@type render_result() :: %{
  content: rendered(),
  metadata: map(),
  context: AshReports.RenderContext.t()
}

rendered()

@type rendered() :: String.t() | binary()

report_module()

@type report_module() :: module()

Callbacks

cleanup(t, render_result)

(optional)
@callback cleanup(AshReports.RenderContext.t(), render_result()) :: :ok

Cleans up after rendering operations.

Optional callback for cleanup.

content_type()

@callback content_type() :: String.t()

The MIME content type for this format.

file_extension()

@callback file_extension() :: String.t()

The file extension for this format.

prepare(t, opts)

(optional)
@callback prepare(AshReports.RenderContext.t(), opts()) ::
  {:ok, AshReports.RenderContext.t()} | {:error, term()}

Prepares the renderer for rendering operations.

Optional callback for initialization.

render(report_module, data, opts)

(optional)
@callback render(report_module(), data(), opts()) :: {:ok, rendered()} | {:error, term()}

Legacy render callback for backward compatibility.

Prefer implementing render_with_context/2 for new renderers.

render_with_context(t, opts)

@callback render_with_context(AshReports.RenderContext.t(), opts()) ::
  {:ok, render_result()} | {:error, term()}

Enhanced render callback with context support.

This is the preferred implementation for Phase 3.1 renderers.

supports_streaming?()

@callback supports_streaming?() :: boolean()

Whether this renderer supports streaming output.

validate_context(t)

(optional)
@callback validate_context(AshReports.RenderContext.t()) :: :ok | {:error, term()}

Validates that the renderer can handle the given context.

Optional callback for advanced validation.

Functions

create_context(report, data_result, config \\ %{})

@spec create_context(any(), map(), map()) :: AshReports.RenderContext.t()

Creates a RenderContext from a report and DataLoader result.

Convenience function for creating context from Phase 2 output.

Examples

{:ok, data_result} = DataLoader.load_report(domain, :report_name, params)
context = Renderer.create_context(report, data_result, config)

get_available_renderers(report_module)

@spec get_available_renderers(module()) :: [atom()]

Gets available renderers for a report module.

Examples

renderers = Renderer.get_available_renderers(MyReport)

get_renderer_info(report_module, format)

@spec get_renderer_info(module(), atom()) :: map() | nil

Gets renderer information for a specific format.

Examples

info = Renderer.get_renderer_info(MyReport, :html)

render(report_module, data, format, opts \\ [])

@spec render(module(), any(), atom(), Keyword.t()) :: {:ok, any()} | {:error, term()}

Renders a report using the appropriate renderer for the given format.

Legacy API for backward compatibility. Prefer render_with_context/3.

render_from_data_result(renderer, report, data_result, config \\ %{}, opts \\ [])

@spec render_from_data_result(module(), any(), map(), map(), opts()) ::
  {:ok, render_result()} | {:error, term()}

Renders a report directly from DataLoader results.

High-level API that combines context creation and rendering.

Examples

{:ok, data_result} = DataLoader.load_report(domain, :sales_report, params)
{:ok, output} = Renderer.render_from_data_result(
  renderer,
  report,
  data_result,
  config
)

render_with_context(renderer, context, opts \\ [])

@spec render_with_context(module(), AshReports.RenderContext.t(), opts()) ::
  {:ok, render_result()} | {:error, term()}

Renders a report using the enhanced context-aware API.

This is the main entry point for Phase 3.1 rendering with full context and layout support.

Examples

context = RenderContext.new(report, data_result, config)
{:ok, result} = Renderer.render_with_context(renderer, context)

supports_format?(report_module, format)

@spec supports_format?(module(), atom()) :: boolean()

Checks if a renderer supports the given format.

Examples

if Renderer.supports_format?(MyReport, :pdf) do
  render_as_pdf()
end

validate_renderer_module(renderer)

@spec validate_renderer_module(module()) :: :ok | {:error, [atom()]}

Validates a renderer module implements the required callbacks.

Examples

case Renderer.validate_renderer_module(MyRenderer) do
  :ok -> use_renderer(MyRenderer)
  {:error, missing} -> handle_missing_callbacks(missing)
end