AshIntrospection.TypeSystem.Introspection (AshIntrospection v0.3.0)

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.

Summary

Functions

Builds a reverse mapping from client names to internal names.

Classifies an Ash type into a category for processing purposes.

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.

Gets the typescript_field_names as a map, or empty map if not available.

Extracts union types from an attribute's constraints.

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 module has a typescript_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 type is a custom Ash type with a typescript_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}

classify_ash_type(type_module, attribute, is_array)

Classifies an Ash type into a category for processing purposes.

Returns one of:

  • :union_attribute - Union type
  • :embedded_resource - Single embedded resource
  • :embedded_resource_array - Array of embedded resources
  • :tuple - Tuple type
  • :attribute - Simple attribute (default)

Parameters

  • type_module - The Ash type module (e.g., Ash.Type.String, Ash.Type.Union)
  • attribute - The attribute struct containing type and constraints
  • is_array - Whether this is inside an array type

Examples

iex> attr = %{type: MyApp.Address, constraints: []}
iex> AshIntrospection.TypeSystem.Introspection.classify_ash_type(MyApp.Address, attr, false)
:embedded_resource

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_typescript_field_names_map(module)

Gets the typescript_field_names as a map, or empty map if not available.

Falls back to interop_field_names if typescript_field_names is not available.

Examples

iex> AshIntrospection.TypeSystem.Introspection.get_typescript_field_names_map(MyApp.TaskStats)
%{is_active?: "isActive", meta_1: "meta1"}

get_union_types(attribute)

Extracts union types from an attribute's constraints.

Handles both direct union types and array union types.

Examples

iex> attr = %{type: Ash.Type.Union, constraints: [types: [note: [...], url: [...]]]}
iex> AshIntrospection.TypeSystem.Introspection.get_union_types(attr)
[note: [...], url: [...]]

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

has_typescript_field_names?(module)

Checks if a module has a typescript_field_names/0 callback.

This is TypeScript-specific but included here for compatibility with modules that use the TypeScript-specific callback instead of the generalized interop_field_names callback.

Examples

iex> AshIntrospection.TypeSystem.Introspection.has_typescript_field_names?(MyApp.TaskStats)
true

iex> AshIntrospection.TypeSystem.Introspection.has_typescript_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_custom_typescript_type?(type)

Checks if a type is a custom Ash type with a typescript_type_name callback.

This is TypeScript-specific but included for compatibility.

Examples

iex> AshIntrospection.TypeSystem.Introspection.is_custom_typescript_type?(MyApp.MyCustomType)
true

is_embedded_resource?(module)

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)

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