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
@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()}
@type format_type() ::
:auto
| :number
| :currency
| :percentage
| :date
| :time
| :datetime
| :boolean
| :string
| :custom
Functions
@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"
@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 formatoptions- 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"]
@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 recordfield_specs- Field formatting specificationsoptions- 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)
@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"}}
@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 formatfield_specs- Keyword list of field names and their formatting optionsoptions- 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%"
# }
@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 formatoptions- 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"}
@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 formatpattern- The custom format pattern stringlocale- 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"}
@spec format_with_spec( term(), AshReports.FormatSpecification.format_spec_name() | AshReports.FormatSpecification.format_spec(), String.t(), [format_option()] ) :: format_result()
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 formatformat_spec- Format specification name or compiled specificationlocale- 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)
@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]
@spec register_format_spec( AshReports.FormatSpecification.format_spec_name(), AshReports.FormatSpecification.format_spec() ) :: :ok | {:error, term()}
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 specificationspec- 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)