ash_state_machine/lib/ash_fsm.ex

119 lines
3 KiB
Elixir
Raw Normal View History

2023-04-22 05:43:36 +12:00
defmodule AshFsm do
@moduledoc """
Documentation for `AshFsm`.
"""
defmodule Event do
@moduledoc """
The configuration for an event.
"""
2023-04-22 07:25:39 +12:00
@type t :: %__MODULE__{
action: atom,
from: [atom],
to: [atom]
}
2023-04-22 05:43:36 +12:00
defstruct [:action, :from, :to]
end
@event %Spark.Dsl.Entity{
name: :event,
target: Event,
args: [:action],
identifier: :action,
schema: [
action: [
type: :atom,
doc: "The corresponding action that is invoked for the event."
],
from: [
type: {:or, [{:list, :atom}, :atom]},
doc:
"The states in which this action may be called. If not specified, then any state is accepted."
],
to: [
type: {:or, [{:list, :atom}, :atom]},
doc:
"The states that this action may move to. If not specified, then any state is accepted."
]
]
}
@events %Spark.Dsl.Section{
name: :events,
entities: [
@event
]
}
@fsm %Spark.Dsl.Section{
name: :fsm,
schema: [
2023-04-22 07:25:39 +12:00
deprecated_states: [
type: {:list, :atom},
doc: """
A list of states that have been deprecated.
The list of states is derived from the events normally.
Use this option to express that certain types should still
be included even though no events go to/from that state anymore.
"""
],
2023-04-22 05:43:36 +12:00
state_attribute: [
type: :atom,
doc: "The attribute to store the state in.",
default: :state
],
initial_states: [
type: {:or, [{:list, :atom}, :atom]},
doc:
"The allowed starting states of this state machine. If not specified, all states are allowed."
2023-04-22 07:25:39 +12:00
],
default_initial_state: [
type: :atom,
doc: "The default initial state"
2023-04-22 05:43:36 +12:00
]
],
sections: [
@events
]
}
2023-04-22 07:25:39 +12:00
use Spark.Dsl.Extension,
sections: [@fsm],
transformers: [
AshFsm.Transformers.FillInEventDefaults,
AshFsm.Transformers.AddState,
AshFsm.Transformers.EnsureStateSelected
],
verifiers: [
AshFsm.Verifiers.VerifyEventActions,
AshFsm.Verifiers.VerifyDefaultInitialState
],
imports: [
AshFsm.BuiltinChanges
]
def transition_state(%{action_type: :update} = changeset, target) do
events = AshFsm.Info.fsm_events(changeset.resource, changeset.action.name)
attribute = AshFsm.Info.fsm_state_attribute!(changeset.resource)
old_state = Map.get(changeset.data, attribute)
case Enum.find(events, fn event ->
old_state in List.wrap(event.from) and target in List.wrap(event.to)
end) do
nil ->
Ash.Changeset.add_error(
changeset,
AshFsm.Errors.NoMatchingEvent.exception(
from: old_state,
target: target,
action: changeset.action.name
)
)
_event ->
Ash.Changeset.force_change_attribute(changeset, attribute, target)
end
end
2023-04-22 05:43:36 +12:00
end