Core type introspection and classification for Ash types.
This module provides a centralized set of functions for determining the nature and characteristics of Ash types, including embedded resources, typed structs, unions, and primitive types.
Used throughout the codebase for type checking, code generation, and runtime processing.
The two functions that ask whether a module is a resource —
is_embedded_resource?/2 and is_resource_instance_of?/2 — take an
optional trailing config and read through AshIntrospection.ResourceInfo.
Both use its runtime reading: a module the manifest does not carry falls back
to live introspection rather than being reclassified as a non-resource. See
that module's docs for why the fallback is not optional in the request path.
Summary
Functions
Builds a reverse mapping from client names to internal names.
Gets field names map using the specified callback, with fallback to interop_field_names.
Gets the type and constraints for a field from field specs.
Extracts the inner type from an array type.
Gets the interop_field_names as a map, or empty map if not available.
Gets the interop type name for a custom type, or nil if not available.
Extracts union types from type and constraints directly.
Checks if constraints include non-empty field definitions.
Checks if a module has a field names callback of the specified type.
Checks if a module has an interop_field_names/0 callback.
Checks if a type is an Ash type module.
Checks if a type is a custom Ash type with an interop_type_name callback.
Checks if a module is an embedded Ash resource.
Checks if a type is a primitive Ash type (not a complex or composite type).
Checks if constraints specify an instance_of that is an Ash resource.
Recursively unwraps Ash.Type.NewType to get the underlying type and constraints.
Functions
Builds a reverse mapping from client names to internal names.
Can take either a map of field names or a module with interop_field_names/0.
Examples
iex> AshIntrospection.TypeSystem.Introspection.build_reverse_field_names_map(%{is_active?: "isActive"})
%{"isActive" => :is_active?}
iex> AshIntrospection.TypeSystem.Introspection.build_reverse_field_names_map(MyApp.TaskStats)
%{"isActive" => :is_active?, "meta1" => :meta_1}
Gets field names map using the specified callback, with fallback to interop_field_names.
Parameters
module- The module to get field names fromcallback- The callback name to use (e.g., :typescript_field_names)
Examples
iex> get_field_names_map(MyApp.TaskStats, :typescript_field_names)
%{is_active?: "isActive", meta_1: "meta1"}
Gets the type and constraints for a field from field specs.
Examples
iex> specs = [name: [type: :string], age: [type: :integer]]
iex> AshIntrospection.TypeSystem.Introspection.get_field_spec_type(specs, :name)
{:string, []}
iex> AshIntrospection.TypeSystem.Introspection.get_field_spec_type(specs, :unknown)
{nil, []}
Extracts the inner type from an array type.
Examples
iex> AshIntrospection.TypeSystem.Introspection.get_inner_type({:array, Ash.Type.String})
Ash.Type.String
iex> AshIntrospection.TypeSystem.Introspection.get_inner_type(Ash.Type.String)
Ash.Type.String
Gets the interop_field_names as a map, or empty map if not available.
This is the generalized version that works with all language generators.
Examples
iex> AshIntrospection.TypeSystem.Introspection.get_interop_field_names_map(MyApp.TaskStats)
%{is_active?: "isActive", meta_1: "meta1"}
iex> AshIntrospection.TypeSystem.Introspection.get_interop_field_names_map(Ash.Type.String)
%{}
Gets the interop type name for a custom type, or nil if not available.
Examples
iex> AshIntrospection.TypeSystem.Introspection.get_interop_type_name(MyApp.MyCustomType)
"MyCustomType"
iex> AshIntrospection.TypeSystem.Introspection.get_interop_type_name(Ash.Type.String)
nil
Extracts union types from type and constraints directly.
Useful when you have constraints but not the full attribute struct. Handles both direct union types and array union types.
Examples
iex> constraints = [types: [note: [...], url: [...]]]
iex> AshIntrospection.TypeSystem.Introspection.get_union_types_from_constraints(Ash.Type.Union, constraints)
[note: [...], url: [...]]
Checks if constraints include non-empty field definitions.
Examples
iex> AshIntrospection.TypeSystem.Introspection.has_field_constraints?([fields: [name: [type: :string]]])
true
iex> AshIntrospection.TypeSystem.Introspection.has_field_constraints?([fields: []])
false
Checks if a module has a field names callback of the specified type.
Parameters
module- The module to checkcallback- Either an atom callback name (e.g., :interop_field_names) ora function `(module -> boolean)` for custom detection
Examples
iex> has_field_names_callback?(MyApp.TaskStats, :interop_field_names)
true
iex> has_field_names_callback?(MyApp.TaskStats, :typescript_field_names)
true
Checks if a module has an interop_field_names/0 callback.
This is the generalized callback that works with all language generators (TypeScript, Kotlin, etc.).
Examples
iex> AshIntrospection.TypeSystem.Introspection.has_interop_field_names?(MyApp.TaskStats)
true
iex> AshIntrospection.TypeSystem.Introspection.has_interop_field_names?(Ash.Type.String)
false
Checks if a type is an Ash type module.
Examples
iex> AshIntrospection.TypeSystem.Introspection.is_ash_type?(Ash.Type.String)
true
iex> AshIntrospection.TypeSystem.Introspection.is_ash_type?(MyApp.CustomType)
true
iex> AshIntrospection.TypeSystem.Introspection.is_ash_type?(:string)
false
Checks if a type is a custom Ash type with an interop_type_name callback.
Custom types are Ash types that define an interop_type_name/0 callback
to specify their representation in generated code.
Examples
iex> AshIntrospection.TypeSystem.Introspection.is_custom_interop_type?(MyApp.MyCustomType)
true
iex> AshIntrospection.TypeSystem.Introspection.is_custom_interop_type?(Ash.Type.String)
false
@spec is_embedded_resource?(term(), AshIntrospection.ResourceInfo.config()) :: boolean()
Checks if a module is an embedded Ash resource.
Examples
iex> AshIntrospection.TypeSystem.Introspection.is_embedded_resource?(MyApp.Accounts.Address)
true
iex> AshIntrospection.TypeSystem.Introspection.is_embedded_resource?(MyApp.Accounts.User)
false
Checks if a type is a primitive Ash type (not a complex or composite type).
Primitive types include basic types like String, Integer, Boolean, Date, UUID, etc.
Examples
iex> AshIntrospection.TypeSystem.Introspection.is_primitive_type?(Ash.Type.String)
true
iex> AshIntrospection.TypeSystem.Introspection.is_primitive_type?(Ash.Type.Union)
false
@spec is_resource_instance_of?(term(), AshIntrospection.ResourceInfo.config()) :: boolean()
Checks if constraints specify an instance_of that is an Ash resource.
Examples
iex> AshIntrospection.TypeSystem.Introspection.is_resource_instance_of?([instance_of: MyApp.Todo])
true
iex> AshIntrospection.TypeSystem.Introspection.is_resource_instance_of?([])
false
Recursively unwraps Ash.Type.NewType to get the underlying type and constraints.
When a type is wrapped in one or more NewType wrappers, this function
recursively unwraps them until it reaches the base type. If the NewType
has a callback for field names and the constraints don't already
have an instance_of key, it will add the NewType module as instance_of
to preserve the reference for field name mapping.
Parameters
type- The type to unwrap (e.g., MyApp.CustomType)constraints- The constraints for the typefield_names_callback- The callback name to check for field name mappings (default: :interop_field_names)
Returns
A tuple {unwrapped_type, unwrapped_constraints} where:
unwrapped_typeis the final underlying type after all NewType unwrappingunwrapped_constraintsare the final constraints, potentially augmented withinstance_of
Examples
iex> # Simple NewType with field_names callback
iex> unwrap_new_type(MyApp.TaskStats, [], :interop_field_names)
{Ash.Type.Struct, [fields: [...], instance_of: MyApp.TaskStats]}
iex> # Non-NewType (returns unchanged)
iex> unwrap_new_type(Ash.Type.String, [max_length: 50], :interop_field_names)
{Ash.Type.String, [max_length: 50]}