AshReports.Formatter (ash_reports v0.1.0)

Locale-aware formatting utilities for AshReports.

This module provides comprehensive formatting functions that integrate with the CLDR backend to deliver locale-appropriate formatting for numbers, currencies, dates, and other data types used in reporting.

Features

  • Number Formatting: Decimal, percentage, and scientific notation with locale rules
  • Currency Formatting: Multi-currency support with proper symbols and placement
  • Date/Time Formatting: Locale-specific date and time representations
  • Data Type Detection: Automatic format selection based on data type
  • Performance Optimization: Caching and efficient format application
  • Error Recovery: Graceful fallbacks when formatting fails

Integration with Renderers

The Formatter module is designed to integrate seamlessly with all Phase 3 renderers:

  • HTML Renderer: CSS-compatible formatting with proper direction support
  • PDF Renderer: Print-optimized formatting with typography considerations
  • JSON Renderer: Structured data with formatted display values
  • HEEX Renderer: LiveView-compatible formatting with assigns integration

Usage Patterns

Basic Value Formatting

# Format a single value with auto-detection
{:ok, formatted} = Formatter.format_value(1234.56, locale: "fr")
# => "1 234,56"

# Format with explicit type
{:ok, formatted} = Formatter.format_value(1234.56, type: :currency, currency: :EUR, locale: "fr")
# => "1 234,56 €"

Record Field Formatting

# Format specific fields in a record
record = %{amount: 1500.00, date: ~D[2024-03-15], count: 42}
formatted = Formatter.format_record(record, [
  amount: [type: :currency, currency: :USD],
  date: [type: :date, format: :long],
  count: [type: :number]
], locale: "en")

Batch Formatting

# Format multiple values efficiently
values = [1234.56, 2345.67, 3456.78]
formatted = Formatter.format_batch(values, type: :currency, currency: :USD, locale: "en")

Configuration

The formatter can be configured globally or per-operation:

# Global configuration
config :ash_reports, AshReports.Formatter,
  default_locale: "en",
  currency_precision: 2,
  date_format: :medium,
  number_format: :decimal

# Per-operation configuration
options = [
  locale: "fr",
  type: :currency,
  currency: :EUR,
  precision: 2
]

Performance Considerations

  • Format Caching: Compiled formatters are cached per locale
  • Batch Operations: Multiple values can be formatted efficiently
  • Lazy Loading: Locale data is loaded on-demand
  • Memory Management: Format cache is automatically cleaned up

Error Handling

All formatting functions return {:ok, result} or {:error, reason} tuples. When formatting fails, the original value is returned with a warning logged.

Summary

Functions

Gets the appropriate CSS class for a formatted value based on its type and locale.

Formats a list of values efficiently using batch processing.

Formats a data structure (list of records) for rendering.

Formats a value specifically for JSON output, ensuring proper serialization while maintaining locale-aware display formatting.

Formats multiple fields in a record according to field-specific formatting rules.

Formats a single value with automatic type detection or explicit type specification.

Formats a value using a custom pattern string.

Formats a value using a custom format specification.

Gets a list of all registered format specifications.

Registers a format specification for reuse by name.

Types

format_option()

@type format_option() ::
  {:locale, String.t()}
  | {:type, format_type()}
  | {:currency, atom()}
  | {:precision, non_neg_integer()}
  | {:format, atom()}
  | {:timezone, String.t()}
  | {:format_spec,
     AshReports.FormatSpecification.format_spec_name()
     | AshReports.FormatSpecification.format_spec()}
  | {:custom_pattern, String.t()}

format_result()

@type format_result() :: {:ok, String.t()} | {:error, term()}

format_type()

@type format_type() ::
  :auto
  | :number
  | :currency
  | :percentage
  | :date
  | :time
  | :datetime
  | :boolean
  | :string
  | :custom

Functions

css_class_for_type(type, locale \\ nil)

@spec css_class_for_type(format_type(), String.t()) :: String.t()

Gets the appropriate CSS class for a formatted value based on its type and locale.

This is useful for HTML rendering where different value types may need different styling (e.g., right-aligned numbers, currency symbols).

Examples

iex> AshReports.Formatter.css_class_for_type(:currency, "en")
"currency-value text-right"

iex> AshReports.Formatter.css_class_for_type(:date, "ar")
"date-value text-left"

format_batch(values, options \\ [])

@spec format_batch([term()], [format_option()]) ::
  {:ok, [String.t()]} | {:error, term()}

Formats a list of values efficiently using batch processing.

Parameters

  • values - List of values to format
  • options - Formatting options applied to all values

Examples

values = [1234.56, 2345.67, 3456.78]

{:ok, formatted} = AshReports.Formatter.format_batch(values, 
  type: :currency, currency: :USD, locale: "en")
# => ["$1,234.56", "$2,345.67", "$3,456.78"]

format_data(data, field_specs, options \\ [])

@spec format_data([map()] | map(), keyword(), [format_option()]) ::
  {:ok, [map()] | map()} | {:error, term()}

Formats a data structure (list of records) for rendering.

This is a convenience function that combines record and batch formatting for complex data structures commonly used in reports.

Parameters

  • data - List of records or single record
  • field_specs - Field formatting specifications
  • options - Global formatting options

Examples

data = [
  %{name: "Product A", price: 99.99, date: ~D[2024-03-15]},
  %{name: "Product B", price: 149.99, date: ~D[2024-03-16]}
]

field_specs = [
  price: [type: :currency, currency: :USD],
  date: [type: :date, format: :short]
]

{:ok, formatted} = AshReports.Formatter.format_data(data, field_specs)

format_for_json(value, options \\ [])

@spec format_for_json(term(), [format_option()]) :: {:ok, map()} | {:error, term()}

Formats a value specifically for JSON output, ensuring proper serialization while maintaining locale-aware display formatting.

Returns a map with both the original value and formatted display string.

Examples

iex> AshReports.Formatter.format_for_json(1234.56, type: :currency, currency: :USD)
{:ok, %{value: 1234.56, formatted: "$1,234.56", type: "currency"}}

format_record(record, field_specs, options \\ [])

@spec format_record(map(), keyword(), [format_option()]) ::
  {:ok, map()} | {:error, term()}

Formats multiple fields in a record according to field-specific formatting rules.

Parameters

  • record - The record (map) containing fields to format
  • field_specs - Keyword list of field names and their formatting options
  • options - Global formatting options applied to all fields

Examples

record = %{
  amount: 1500.00,
  date: ~D[2024-03-15],
  count: 42,
  rate: 0.0525
}

field_specs = [
  amount: [type: :currency, currency: :USD],
  date: [type: :date, format: :medium],
  count: [type: :number],
  rate: [type: :percentage, precision: 2]
]

{:ok, formatted} = AshReports.Formatter.format_record(record, field_specs)
# => %{
#   amount: "$1,500.00",
#   date: "Mar 15, 2024", 
#   count: "42",
#   rate: "5.25%"
# }

format_value(value, options \\ [])

@spec format_value(term(), [format_option()]) :: format_result()

Formats a single value with automatic type detection or explicit type specification.

Parameters

  • value - The value to format
  • options - Formatting options (see module documentation)

Examples

iex> AshReports.Formatter.format_value(1234.56)
{:ok, "1,234.56"}

iex> AshReports.Formatter.format_value(1234.56, locale: "fr")
{:ok, "1 234,56"}

iex> AshReports.Formatter.format_value(1234.56, type: :currency, currency: :EUR)
{:ok, "$1,234.56"}

iex> AshReports.Formatter.format_value(~D[2024-03-15], type: :date, format: :long)
{:ok, "March 15, 2024"}

iex> AshReports.Formatter.format_value(1234.56, custom_pattern: "#,##0.000")
{:ok, "1,234.560"}

format_with_custom_pattern(value, pattern, locale \\ nil, options \\ [])

@spec format_with_custom_pattern(term(), String.t(), String.t(), [format_option()]) ::
  format_result()

Formats a value using a custom pattern string.

Provides direct formatting using pattern strings without requiring a formal format specification definition.

Parameters

  • value - The value to format
  • pattern - The custom format pattern string
  • locale - Locale for formatting (default: current locale)
  • options - Additional formatting options

Examples

iex> AshReports.Formatter.format_with_custom_pattern(1234.56, "#,##0.000")
{:ok, "1,234.560"}

iex> AshReports.Formatter.format_with_custom_pattern(1234.56, "¤#,##0.00", "en", currency: :EUR)
{:ok, "€1,234.56"}

format_with_spec(value, format_spec, locale \\ nil, options \\ [])

Formats a value using a custom format specification.

Provides advanced formatting capabilities through format specifications that can include conditional formatting, custom patterns, and complex transformations.

Parameters

  • value - The value to format
  • format_spec - Format specification name or compiled specification
  • locale - Locale for formatting (default: current locale)
  • options - Additional formatting options

Examples

# Using a predefined format specification
spec = AshReports.FormatSpecification.new(:custom_currency, pattern: "¤ #,##0.00")
{:ok, compiled} = AshReports.FormatSpecification.compile(spec)
{:ok, formatted} = AshReports.Formatter.format_with_spec(1234.56, compiled)

# Using conditional formatting
spec = AshReports.FormatSpecification.new(:conditional_number)
|> AshReports.FormatSpecification.add_condition({:>, 1000}, pattern: "#,##0K", color: :green)
|> AshReports.FormatSpecification.set_default_pattern("#,##0.00")
{:ok, compiled} = AshReports.FormatSpecification.compile(spec)
{:ok, formatted} = AshReports.Formatter.format_with_spec(1500, compiled)

list_format_specs()

@spec list_format_specs() :: [AshReports.FormatSpecification.format_spec_name()]

Gets a list of all registered format specifications.

Examples

iex> AshReports.Formatter.list_format_specs()
[:company_currency, :report_number, :conditional_status]

register_format_spec(name, spec)

Registers a format specification for reuse by name.

Allows format specifications to be defined once and referenced by name throughout the application, promoting consistency and reusability.

Parameters

  • name - Unique name for the format specification
  • spec - The format specification to register

Examples

spec = AshReports.FormatSpecification.new(:company_currency, 
  pattern: "¤ #,##0.00",
  currency: :USD
)
AshReports.Formatter.register_format_spec(:company_currency, spec)