AshIntrospection.Rpc.ResultProcessor (AshIntrospection v0.4.0)

Copy Markdown View Source

Extracts requested fields from RPC results using type-driven dispatch.

This module uses the same pattern as ValueFormatter and FieldSelector: type-driven recursive dispatch where each type is self-describing.

Architecture

The core insight is that both ValueFormatter and ResultProcessor need to understand type structure:

  • ValueFormatter: Formats field names (internal ↔ client)
  • ResultProcessor: Extracts requested fields (filtering)

They share the need for type-driven recursive dispatch but have different concerns.

Configuration

This module uses a config map for language-specific customization:

%{
  field_names_callback: :interop_field_names  # or :typescript_field_names
}

Type-Driven Extraction

extract_value/5 (unified type-driven dispatch)
   
   > extract_resource_value/4    (Ash Resources)
   > extract_typed_struct_value/4 (TypedStruct/NewType)
   > extract_typed_map_value/4   (Map/Struct with fields)
   > extract_union_value/4       (Ash.Type.Union)
   > extract_array_value/5       (Arrays - recurse)
   > normalize_primitive/1       (Primitives)

Summary

Functions

Determines the type and constraints for a given data value.

Extracts and normalizes a value based on its type and template.

Gets the type and constraints for a field, checking all field sources.

Normalizes a value for JSON serialization.

Main entry point for processing Ash results.

Types

config()

@type config() :: %{
  optional(:field_names_callback) => atom(),
  optional(:manifest) =>
    Ash.Info.Manifest.t() | AshIntrospection.ResourceInfo.Source.t() | nil
}

Functions

determine_data_type(data, resource, config)

Determines the type and constraints for a given data value.

This function infers type information from:

  1. The struct type of the data itself (if it's a struct)
  2. The provided resource context
  3. Falls back to nil for unknown types

extract_value(value, type, constraints, template, config)

@spec extract_value(term(), atom() | tuple() | nil, keyword(), list(), config()) ::
  term()

Extracts and normalizes a value based on its type and template.

This is the core recursive function that dispatches to type-specific handlers based on the type's characteristics. Mirrors the pattern used in ValueFormatter.format/5.

Parameters

  • value - The value to extract from
  • type - The Ash type (or nil for unknown)
  • constraints - Type constraints
  • template - The extraction template (list of field specs)
  • config - Configuration map

Returns

The extracted and normalized value.

get_field_type_info(resource, field_name, config)

@spec get_field_type_info(module() | nil, atom(), config()) ::
  {atom() | tuple() | nil, keyword()}

Gets the type and constraints for a field, checking all field sources.

This consolidates all the previous resource lookup functions into one.

Parameters

  • resource - The Ash resource module, TypedStruct module, or nil
  • field_name - The field name (atom)
  • config - Configuration map with field_names_callback

Returns

{type, constraints} or {nil, []} if not found.

normalize_primitive(value)

Normalizes a value for JSON serialization.

Handles DateTime, Date, Time, Decimal, CiString, atoms, keyword lists, nested maps, regular lists, and Ash.Union types. Recursively normalizes nested structures.

%Ash.ForbiddenField{} and %Ash.NotLoaded{} normalize to nil. Inside a struct, map or keyword list a not-loaded field is omitted rather than sent as nil, so the client can tell "you may not see this" apart from "this was not loaded" — the same distinction the templated path draws in extract_value/5.

process(result, extraction_template, resource \\ nil, config \\ %{})

@spec process(term(), map(), module() | nil, config()) :: term()

Main entry point for processing Ash results.