Handles field name formatting for input parameters, output fields, and TypeScript generation.
Supports built-in formatters and custom formatter functions.
Summary
Functions
Formats a field name for client output, optionally applying resource/type-level field_names mapping.
Formats a field name using the configured formatter.
Formats a map of fields, converting all keys using the specified formatter.
Recursively formats all keys in a nested structure for client consumption.
Formats a sort string by converting field names from client format to internal format.
Parses input field names from client format to internal format.
Parses a map of input fields, converting all keys from client format to internal format.
Recursively parses input values, handling nested structures.
Resolves a field name to its existing atom, applying the formatter for case conversion.
Functions
Formats a field name for client output, optionally applying resource/type-level field_names mapping.
Use this when formatting field names for client consumption where the field
might have a custom TypeScript name via the field_names DSL option or the
typescript_field_names callback function.
Examples
iex> AshTypescript.FieldFormatter.format_field_for_client(:user_name, nil, :camel_case)
"userName"
iex> AshTypescript.FieldFormatter.format_field_for_client("already_string", nil, :camel_case)
"alreadyString"When a resource or type module is provided with field_names/typescript_field_names mappings
(e.g., :is_active? → "isActive"), the mapped string value is used directly WITHOUT
additional formatting.
Formats a field name using the configured formatter.
Examples
iex> AshTypescript.FieldFormatter.format_field_name(:user_name, :camel_case)
"userName"
iex> AshTypescript.FieldFormatter.format_field_name(:user_name, :snake_case)
"user_name"
iex> AshTypescript.FieldFormatter.format_field_name("user_name", :pascal_case)
"UserName"
Formats a map of fields, converting all keys using the specified formatter.
Examples
iex> AshTypescript.FieldFormatter.format_fields(%{user_name: "John", user_email: "john@example.com"}, :camel_case)
%{"userName" => "John", "userEmail" => "john@example.com"}
Recursively formats all keys in a nested structure for client consumption.
Walks maps and lists, converting every key with the given formatter. Structs and primitives are returned untouched, and non-atom/non-binary keys are left as-is.
Used for any payload that is handed to the client without going through a type-driven formatter — RPC responses and typed controller error bodies both rely on this so their field names agree.
Examples
iex> AshTypescript.FieldFormatter.format_output_field_names(%{short_message: "x"}, :camel_case)
%{"shortMessage" => "x"}
iex> AshTypescript.FieldFormatter.format_output_field_names([%{user_name: "a"}], :camel_case)
[%{"userName" => "a"}]
Formats a sort string by converting field names from client format to internal format.
Handles Ash.Query.sort_input format:
- "name" or "+name" (ascending)
- "++name" (ascending with nils first)
- "-name" (descending)
- "--name" (descending with nils last)
- "-name,++title" (multiple fields with different modifiers)
Preserves sort modifiers while converting field names using the input formatter.
Examples
iex> AshTypescript.FieldFormatter.format_sort_string("--startDate,++insertedAt", :camel_case)
"--start_date,++inserted_at"
iex> AshTypescript.FieldFormatter.format_sort_string("-userName", :camel_case)
"-user_name"
iex> AshTypescript.FieldFormatter.format_sort_string(nil, :camel_case)
nil
Parses input field names from client format to internal format.
This is used for converting incoming client field names to the internal Elixir atom keys that Ash expects.
Examples
iex> AshTypescript.FieldFormatter.parse_input_field("userName", :camel_case)
:user_name
Parses a map of input fields, converting all keys from client format to internal format.
Recursively processes nested maps and arrays to ensure all field names are properly formatted. This is essential for union types and embedded resources that contain nested field structures.
Examples
iex> AshTypescript.FieldFormatter.parse_input_fields(%{"userName" => "John", "userEmail" => "john@example.com"}, :camel_case)
%{user_name: "John", user_email: "john@example.com"}
iex> AshTypescript.FieldFormatter.parse_input_fields(%{"attachments" => [%{"mimeType" => "pdf", "attachmentType" => "file"}]}, :camel_case)
%{attachments: [%{mime_type: "pdf", attachment_type: "file"}]}
Recursively parses input values, handling nested structures.
This function ensures that all nested maps and arrays containing maps have their field names properly formatted according to the formatter.
Only handles JSON-decoded data (maps, lists, primitives) - no structs.
Resolves a field name to its existing atom, applying the formatter for case conversion.
Atoms are passed through unchanged. Every valid string field name corresponds to an atom that already exists (resource attributes, relationships, calculations, and aggregates are all defined at compile time), so a string resolves to an existing atom when one is available and otherwise returns the formatted string unchanged. Downstream field selection compares the result against the known field atoms, so an unresolved name simply fails as an unknown field.
This deliberately never calls String.to_atom/1: client-supplied field names
are attacker-controllable, and minting a fresh atom per name would allow atom
table exhaustion (a node-wide denial of service).
Examples
iex> AshTypescript.FieldFormatter.resolve_field_name("userName", :camel_case)
:user_name
iex> AshTypescript.FieldFormatter.resolve_field_name(:user_name, :camel_case)
:user_name