• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

phcurado / parameter / 85a7d12e6375bc10c9e2667c24d357ec94f3ff9f

24 Jul 2025 01:01PM UTC coverage: 98.661% (-0.9%) from 99.546%
85a7d12e6375bc10c9e2667c24d357ec94f3ff9f

push

github

web-flow
Cross field validation in validator (#90)

13 of 15 new or added lines in 2 files covered. (86.67%)

2 existing lines in 1 file now uncovered.

442 of 448 relevant lines covered (98.66%)

236.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.3
/lib/parameter/schema_fields.ex
1
defmodule Parameter.SchemaFields do
2
  @moduledoc false
3

4
  alias Parameter.Dumper
5
  alias Parameter.Field
6
  alias Parameter.Loader
7
  alias Parameter.Meta
8
  alias Parameter.Types
9
  alias Parameter.Validator
10

11
  @spec process_map_value(Meta.t(), Field.t(), Keyword.t()) ::
12
          {:ok, :ignore} | {:ok, map()} | {:ok, list()} | :ok | {:error, String.t()}
13
  def process_map_value(meta, field, opts) do
14
    exclude_fields = Keyword.get(opts, :exclude)
2,390✔
15

16
    case field_to_exclude(field.name, exclude_fields) do
2,390✔
17
      :include ->
18
        fetch_and_verify_input(meta, field, opts)
2,268✔
19

20
      {:exclude, nested_values} ->
21
        opts = Keyword.put(opts, :exclude, nested_values)
36✔
22
        fetch_and_verify_input(meta, field, opts)
36✔
23

24
      :exclude ->
86✔
25
        {:ok, :ignore}
26
    end
27
  end
28

29
  @spec process_list_value(Meta.t(), list(any()), Keyword.t(), boolean()) ::
30
          {:ok, :ignore} | {:ok, map()} | {:ok, list()} | :ok | {:error, String.t()}
31
  def process_list_value(meta, values, opts, change_parent? \\ true) do
32
    values
33
    |> Enum.with_index()
34
    |> Enum.reduce({[], %{}}, fn {value, index}, {acc_list, errors} ->
35
      meta = meta |> Meta.set_input(value)
730✔
36

37
      meta =
730✔
38
        if change_parent? do
39
          Meta.set_parent_input(meta, value)
28✔
40
        else
41
          meta
702✔
42
        end
43

44
      case operation_handler(meta, meta.schema, value, opts) do
730✔
45
        {:error, reason} ->
106✔
46
          {acc_list, Map.put(errors, index, reason)}
47

48
        {:ok, result} ->
470✔
49
          {[result | acc_list], errors}
50

51
        :ok ->
154✔
52
          {acc_list, errors}
53
      end
54
    end)
55
    |> parse_list_values(meta.operation)
310✔
56
  end
57

58
  @spec field_handler(Meta.t(), atom | Field.t(), any(), Keyword.t()) ::
59
          {:ok, :ignore} | {:ok, map()} | {:ok, list()} | :ok | {:error, String.t()}
60
  def field_handler(_meta, %Field{virtual: true}, _value, _opts) do
72✔
61
    {:ok, :ignore}
62
  end
63

64
  def field_handler(meta, %Field{type: {:map, schema}}, value, opts) when is_map(value) do
65
    if Types.base_type?(schema) or Types.composite_type?(schema) do
208✔
66
      value
67
      |> Enum.reduce({%{}, %{}}, fn {key, value}, {acc_map, errors} ->
68
        case operation_handler(meta, schema, value, opts) do
116✔
69
          {:error, reason} ->
32✔
70
            {acc_map, Map.put(errors, key, reason)}
71

72
          {:ok, result} ->
56✔
73
            {Map.put(acc_map, key, result), errors}
74

75
          :ok ->
28✔
76
            {acc_map, errors}
77
        end
78
      end)
79
      |> parse_map_values(meta.operation)
68✔
80
    else
81
      meta
82
      |> Meta.set_schema(schema)
83
      |> Meta.set_input(value)
84
      |> operation_handler(schema, value, opts)
140✔
85
    end
86
  end
87

88
  def field_handler(_meta, %Field{type: {:map, _schema}}, _value, _opts) do
10✔
89
    {:error, "invalid map type"}
90
  end
91

92
  def field_handler(meta, %Field{type: {:array, schema}}, values, opts) when is_list(values) do
93
    meta
94
    |> Meta.set_schema(schema)
95
    |> process_list_value(values, opts, false)
296✔
96
  end
97

98
  def field_handler(_meta, %Field{type: {:array, _schema}}, _values, _opts) do
12✔
99
    {:error, "invalid array type"}
100
  end
101

102
  def field_handler(
103
        %Meta{operation: operation} = meta,
104
        %Field{validator: validator} = field,
105
        value,
106
        opts
107
      )
108
      when not is_nil(validator) and operation in [:load, :validate] do
109
    case operation_handler(meta, field, value, opts) do
156✔
110
      {:ok, value} ->
111
        validator
112
        |> run_validator(value, meta.parent_input)
82✔
113
        |> parse_validator_result(operation)
82✔
114

115
      :ok ->
116
        validator
117
        |> run_validator(value, meta.parent_input)
68✔
118
        |> parse_validator_result(operation)
68✔
119

120
      error ->
121
        error
6✔
122
    end
123
  end
124

125
  def field_handler(meta, %Field{type: _type} = field, value, opts) do
126
    operation_handler(meta, field, value, opts)
1,312✔
127
  end
128

129
  def field_handler(meta, type, value, opts) do
130
    operation_handler(meta, %Field{type: type}, value, opts)
482✔
131
  end
132

133
  @spec field_to_exclude(atom() | binary(), list()) :: :exclude | :include | {:exclude, list()}
134
  def field_to_exclude(field_name, exclude_fields) when is_list(exclude_fields) do
135
    exclude_fields
136
    |> Enum.find(fn
137
      {key, _value} -> field_name == key
270✔
138
      key -> field_name == key
376✔
139
    end)
140
    |> case do
2,408✔
141
      nil -> :include
2,276✔
142
      {_key, nested_values} -> {:exclude, nested_values}
40✔
143
      _ -> :exclude
92✔
144
    end
145
  end
146

147
  def field_to_exclude(_field_name, _exclude_fields), do: :include
2✔
148

149
  defp parse_list_values({_result, errors}, :validate) do
150
    if errors == %{} do
82✔
151
      :ok
152
    else
153
      {:error, errors}
154
    end
155
  end
156

157
  defp parse_list_values({result, errors}, _operation) do
158
    if errors == %{} do
228✔
159
      {:ok, Enum.reverse(result)}
160
    else
161
      {:error, errors}
162
    end
163
  end
164

165
  defp parse_map_values({_result, errors}, :validate) do
166
    if errors == %{} do
22✔
167
      :ok
168
    else
169
      {:error, errors}
170
    end
171
  end
172

173
  defp parse_map_values({result, errors}, _operation) do
174
    if errors == %{} do
46✔
175
      {:ok, result}
176
    else
177
      {:error, errors}
178
    end
179
  end
180

181
  defp run_validator({func, args}, value, _parent_input) when is_function(func, 2),
182
    do: do_run_validator(func, [value | [args]])
118✔
183

184
  defp run_validator({func, args}, value, parent_input) when is_function(func, 3),
185
    do: do_run_validator(func, [value, parent_input | [args]])
8✔
186

187
  defp run_validator(func, value, _parent_input) when is_function(func, 1),
188
    do: do_run_validator(func, [value])
16✔
189

190
  defp run_validator(func, value, parent_input) when is_function(func, 2),
191
    do: do_run_validator(func, [value, parent_input])
8✔
192

193
  defp run_validator(func, value, parent_input) do
NEW
194
    case func.(value, parent_input) do
×
UNCOV
195
      :ok -> {:ok, value}
×
UNCOV
196
      error -> error
×
197
    end
198
  end
199

200
  defp do_run_validator(validator, [value | _] = args) do
201
    case apply(validator, args) do
150✔
202
      :ok -> {:ok, value}
112✔
203
      error -> error
38✔
204
    end
205
  end
206

207
  defp parse_validator_result({:ok, value}, :load) do
62✔
208
    {:ok, value}
209
  end
210

211
  defp parse_validator_result({:ok, _value}, :validate) do
54✔
212
    :ok
213
  end
214

215
  defp parse_validator_result(error, _operation) do
216
    error
34✔
217
  end
218

219
  defp operation_handler(meta, %Field{type: type} = field, value, opts) do
220
    cond do
1,950✔
221
      Types.composite_inner_type?(type) ->
222
        field_handler(meta, field, value, opts)
130✔
223

224
      meta.operation == :dump ->
1,820✔
225
        Types.dump(type, value)
468✔
226

227
      meta.operation == :load ->
1,352✔
228
        Types.load(type, value)
912✔
229

230
      meta.operation == :validate ->
440✔
231
        Types.validate(type, value)
440✔
232
    end
233
  end
234

235
  defp operation_handler(meta, schema, value, opts) do
236
    cond do
986✔
237
      Types.base_type?(schema) or Types.composite_type?(schema) ->
986✔
238
        field_handler(meta, schema, value, opts)
482✔
239

240
      meta.operation == :dump ->
504✔
241
        Dumper.dump(meta, opts)
130✔
242

243
      meta.operation == :load ->
374✔
244
        Loader.load(meta, opts)
256✔
245

246
      meta.operation == :validate ->
118✔
247
        Validator.validate(meta, opts)
118✔
248
    end
249
  end
250

251
  defp fetch_and_verify_input(meta, field, opts) do
252
    case fetch_input(meta, field, opts) do
2,304✔
253
      :error ->
254
        check_required(field, :ignore, meta.operation)
336✔
255

256
      {:ok, nil} ->
257
        check_nil(meta, field, opts)
64✔
258

259
      {:ok, ""} ->
260
        check_empty(meta, field, opts)
36✔
261

262
      {:ok, value} ->
263
        field_handler(meta, field, value, opts)
1,846✔
264

265
      {:error, reason} ->
22✔
266
        {:error, reason}
267
    end
268
  end
269

270
  defp fetch_input(%Meta{input: input} = meta, field, opts) do
271
    if has_double_key?(field, input) do
2,304✔
272
      {:error, "field is present as atom and string keys"}
273
    else
274
      do_fetch_input(meta, field, opts)
2,298✔
275
    end
276
  end
277

278
  defp has_double_key?(field, input) do
279
    to_string(field.name) == field.key and Map.has_key?(input, field.name) and
2,304✔
280
      Map.has_key?(input, field.key)
820✔
281
  end
282

283
  defp do_fetch_input(
284
         %Meta{operation: :load, input: input, parent_input: parent_input} = meta,
285
         %Field{on_load: on_load} = field,
286
         opts
287
       )
288
       when not is_nil(on_load) do
289
    value = get_from_key_or_name(input, field)
70✔
290

291
    case on_load.(value, parent_input) do
70✔
292
      {:ok, value} ->
293
        field_handler(meta, field, value, opts)
68✔
294

295
      error ->
296
        error
2✔
297
    end
298
  end
299

300
  defp do_fetch_input(
301
         %Meta{operation: :dump, input: input, parent_input: parent_input} = meta,
302
         %Field{on_dump: on_dump} = field,
303
         opts
304
       )
305
       when not is_nil(on_dump) do
306
    value = get_from_key_or_name(input, field)
24✔
307

308
    case on_dump.(value, parent_input) do
24✔
309
      {:ok, value} ->
310
        field_handler(meta, field, value, opts)
22✔
311

312
      error ->
313
        error
2✔
314
    end
315
  end
316

317
  defp do_fetch_input(%Meta{input: input}, field, _opts) do
318
    fetch_from_key_or_name(input, field)
2,204✔
319
  end
320

321
  defp get_from_key_or_name(input, field) do
322
    Map.get(input, field.key) || Map.get(input, field.name)
94✔
323
  end
324

325
  defp fetch_from_key_or_name(input, field) do
326
    case Map.fetch(input, field.key) do
2,204✔
327
      :error -> Map.fetch(input, field.name)
1,358✔
328
      value -> value
846✔
329
    end
330
  end
331

332
  defp check_required(%Field{required: true, load_default: :ignore}, value, :load)
30✔
333
       when value in [:ignore, nil] do
334
    {:error, "is required"}
335
  end
336

337
  defp check_required(%Field{required: true, dump_default: :ignore}, value, :validate)
6✔
338
       when value in [:ignore, nil] do
339
    {:error, "is required"}
340
  end
341

342
  defp check_required(%Field{load_default: default}, :ignore, :load) when default != :ignore do
70✔
343
    {:ok, default}
344
  end
345

346
  defp check_required(%Field{dump_default: default}, :ignore, :dump) when default != :ignore do
34✔
347
    {:ok, default}
348
  end
349

350
  defp check_required(_field, value, :load) do
136✔
351
    {:ok, value}
352
  end
353

354
  defp check_required(_field, value, :dump) do
90✔
355
    {:ok, value}
356
  end
357

358
  defp check_required(_field, _value, :validate) do
70✔
359
    :ok
360
  end
361

362
  defp check_nil(meta, field, opts) do
363
    if opts[:ignore_nil] do
64✔
364
      check_required(field, :ignore, meta.operation)
28✔
365
    else
366
      check_required(field, nil, meta.operation)
36✔
367
    end
368
  end
369

370
  defp check_empty(meta, field, opts) do
371
    if opts[:ignore_empty] do
36✔
372
      check_required(field, :ignore, meta.operation)
12✔
373
    else
374
      check_required(field, "", meta.operation)
24✔
375
    end
376
  end
377
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc