Skip to content

full_diff change builder crashes on {:array, :time} and other primitive struct types #214

@Straffern

Description

@Straffern

Code of Conduct

  • I agree to follow this project's Code of Conduct

AI Policy

  • I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.

Versions

Elixir: 1.18.4 (compiled with Erlang/OTP 27)
Erlang/OTP: 27 [erts-15.2.7.3]
ash: 3.7.6
ash_paper_trail: 0.5.7
spark: 2.3.12

Operating system

Linux (NixOS btw) - kernel 6.16.0-zen1

Current Behavior

When using the full_diff change builder with attributes that are arrays of primitive struct types (like {:array, :time}, {:array, :date}, {:array, :datetime}), creating or updating records crashes with:

** (ArgumentError) `Time` is not a Spark DSL module.

   (elixir 1.18.4) Time.persisted(:primary_key)
   (spark 2.3.12) lib/spark/dsl/extension.ex:142: Spark.Dsl.Extension.persisted!/3
   (ash_paper_trail 0.5.7) lib/change_builders/full_diff/helpers.ex:102: AshPaperTrail.ChangeBuilders.FullDiff.Helpers.unique_id/2
   (ash_paper_trail 0.5.7) lib/change_builders/full_diff/list_change.ex:123: anonymous fn/4 in AshPaperTrail.ChangeBuilders.FullDiff.ListChange.dump_array/2

The root cause is in lib/change_builders/full_diff/helpers.ex:101-108:

def unique_id(%{__struct__: resource}, dump_value) do
  case Ash.Resource.Info.primary_key(resource) do
    [] ->
      nil

    primary_keys ->
      Enum.reduce(primary_keys, [resource], &(&2 ++ [Map.get(dump_value, &1)]))
  end
end

This function pattern-matches on any struct (%{__struct__: resource}), assuming that all structs are Ash resources. However, Elixir's Time, Date, DateTime, NaiveDateTime, and other standard library types are also structs. When Ash.Resource.Info.primary_key/1 is called on Time (or similar), it internally calls Spark.Dsl.Extension.persisted!/3 which fails because Time is not a Spark DSL module.

Reproduction

#213

Expected Behavior

The full_diff change builder should handle arrays of primitive struct types (:time, :date, :datetime, :naive_datetime, etc.) without crashing. These should be treated as simple values rather than Ash embedded resources.

The unique_id/2 function should check if a struct is actually an Ash resource before calling Ash.Resource.Info.primary_key/1 on it. Non-resource structs should fall through to the simple value clause:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions