/
module.ex
155 lines (125 loc) · 3.19 KB
/
module.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
defmodule Ash.Type.Module do
@constraints [
behaviour: [
type: :atom,
doc: "Allows constraining the module a one which implements a behaviour"
],
protocol: [
type: :atom,
doc: "Allows constraining the module a one which implements a protocol"
]
]
@moduledoc """
Stores a module as a string in the database.
A builtin type that can be referenced via `:module`.
### Constraints
#{Spark.OptionsHelpers.docs(@constraints)}
"""
use Ash.Type
@impl true
def storage_type(_), do: :string
@impl true
def constraints, do: @constraints
def apply_constraints(nil, _), do: :ok
def apply_constraints(value, constraints) do
[]
|> apply_behaviour_constraint(value, constraints[:behaviour])
|> apply_protocol_constraint(value, constraints[:protocol])
|> case do
[] -> {:ok, value}
errors -> {:error, errors}
end
end
defp apply_behaviour_constraint(errors, _module, nil), do: errors
defp apply_behaviour_constraint(errors, module, behaviour) do
if Spark.implements_behaviour?(module, behaviour) do
errors
else
Enum.concat(errors, [
[
message: "module %{module} does not implement the %{behaviour} behaviour",
module: module,
behaviour: behaviour
]
])
end
end
defp apply_protocol_constraint(errors, _module, nil), do: errors
defp apply_protocol_constraint(errors, module, protocol) do
Protocol.assert_protocol!(protocol)
Protocol.assert_impl!(protocol, module)
errors
rescue
ArgumentError ->
Enum.concat(errors, [
[
message: "module %{module} does not implement the %{protocol} protocol",
module: module,
protocol: protocol
]
])
end
@impl true
def cast_input(value, _) when is_atom(value) do
if Code.ensure_loaded?(value) do
{:ok, value}
else
:error
end
end
def cast_input("", _), do: {:ok, nil}
def cast_input("Elixir." <> _ = value, _) do
module = Module.concat([value])
if Code.ensure_loaded?(module) do
{:ok, module}
else
:error
end
end
def cast_input(value, _) when is_binary(value) do
atom = String.to_existing_atom(value)
if Code.ensure_loaded?(atom) do
{:ok, atom}
else
:error
end
rescue
ArgumentError ->
:error
end
def cast_input(_, _), do: :error
@impl true
def cast_stored(nil, _), do: {:ok, nil}
def cast_stored(value, _) when is_atom(value) do
if Code.ensure_loaded?(value) do
{:ok, value}
else
:error
end
end
def cast_stored("Elixir." <> _ = value, _) do
module = Module.concat([value])
if Code.ensure_loaded?(module) do
{:ok, module}
else
:error
end
end
def cast_stored(value, _) when is_binary(value) do
atom = String.to_existing_atom(value)
if Code.ensure_loaded?(atom) do
{:ok, atom}
else
:error
end
rescue
ArgumentError -> :error
end
def cast_stored(_, _), do: :error
@impl true
def dump_to_native(nil, _), do: {:ok, nil}
def dump_to_native(value, _) when is_atom(value) do
{:ok, to_string(value)}
end
def dump_to_native(_, _), do: :error
end