AshIntrospection.TypeSystem.Introspection (AshIntrospection v0.4.0)

Copy Markdown View Source

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

build_reverse_field_names_map(field_names)

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}

get_field_names_map(module, callback)

Gets field names map using the specified callback, with fallback to interop_field_names.

Parameters

  • module - The module to get field names from
  • callback - 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"}

get_field_spec_type(field_specs, field_name)

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, []}

get_inner_type(type)

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

get_interop_field_names_map(module)

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)
%{}

get_interop_type_name(type)

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

get_union_types_from_constraints(type, constraints)

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: [...]]

has_field_constraints?(constraints)

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

has_field_names_callback?(module, callback)

Checks if a module has a field names callback of the specified type.

Parameters

  • module - The module to check
  • callback - Either an atom callback name (e.g., :interop_field_names) or
             a 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

has_interop_field_names?(module)

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

is_ash_type?(module)

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

is_custom_interop_type?(type)

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

is_embedded_resource?(module, config \\ %{})

@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

is_primitive_type?(type)

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

is_resource_instance_of?(constraints, config \\ %{})

@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

unwrap_new_type(type, constraints, field_names_callback \\ :interop_field_names)

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 type
  • field_names_callback - The callback name to check for field name mappings (default: :interop_field_names)

Returns

A tuple {unwrapped_type, unwrapped_constraints} where:

  • unwrapped_type is the final underlying type after all NewType unwrapping
  • unwrapped_constraints are the final constraints, potentially augmented with instance_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]}