Skip to content

Commit

Permalink
temporary fix for conditional field and top level auto key condition,…
Browse files Browse the repository at this point in the history
… based #23

This path is for implementing derive in top level conditional fields
  • Loading branch information
shahryarjb committed Dec 29, 2023
1 parent c39bdb3 commit 0b60bf2
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 22 deletions.
62 changes: 48 additions & 14 deletions lib/helper/derive/derive.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ defmodule MishkaDeveloperTools.Helper.Derive do

defp update_reduced_fields(get_field, parsed_derive, hints, map, acc)
when is_list(parsed_derive) and parsed_derive != [] do
# Temporary way to find it is list conditional or not
list_data? = is_list(get_field) and length(get_field) == length(parsed_derive)

get_field =
if list_data? do
get_field
else
stream = Stream.duplicate(get_field, length(parsed_derive))
Enum.to_list(stream)
end

converted_validated_values =
Enum.zip([parsed_derive, get_field, hints])
|> Enum.map(fn {derive, value, hint} ->
Expand All @@ -50,17 +61,13 @@ defmodule MishkaDeveloperTools.Helper.Derive do
if length(validated_errors) > 0, do: {:error, validated_errors}, else: all_data
end)

errors =
converted_validated_values
|> Enum.filter(&(is_tuple(&1) and elem(&1, 0) == :error))
|> Enum.map(fn {:error, errors} -> errors end)
|> Enum.concat()

Map.put(
acc,
map.field,
if(length(errors) > 0, do: {:error, errors}, else: converted_validated_values)
)
{errors, data} = derive_list_values_and_errors_divider(converted_validated_values)

if list_data? do
Map.put(acc, map.field, if(length(errors) > 0, do: {:error, errors}, else: data))
else
Map.put(acc, map.field, if(length(data) > 0, do: List.first(data), else: {:error, errors}))
end
end

defp update_reduced_fields(get_field, parsed_derive, hint, map, acc) do
Expand All @@ -78,6 +85,16 @@ defmodule MishkaDeveloperTools.Helper.Derive do
Map.put(acc, map.field, converted_validated_values)
end

defp derive_list_values_and_errors_divider(data) do
{error, no_error} =
data
|> Enum.split_with(&(is_tuple(&1) and elem(&1, 0) == :error))

converted_error = Enum.map(error, fn {:error, errors} -> errors end) |> Enum.concat()

{converted_error, no_error}
end

@spec error_handler(map(), list(any())) :: {:error, :bad_parameters, any()}
def error_handler(reduced_fields, extra_error \\ []) do
errors =
Expand Down Expand Up @@ -115,10 +132,27 @@ defmodule MishkaDeveloperTools.Helper.Derive do
def get_derives_from_success_conditional_data(conds) do
Enum.reduce(conds, [], fn
{field, {{:ok, _data}, opts}}, acc ->
get_derive = Keyword.get(opts, :derive, [])
get_hint = Keyword.get(opts, :hint, [])
case Keyword.keyword?(opts) do
true ->
get_derive = Keyword.get(opts, :derive, [])
get_hint = Keyword.get(opts, :hint, [])
acc ++ [Map.new([{:derive, get_derive}, {:field, field}, {:hint, get_hint}])]

false when is_list(opts) ->
%{derive: derives, hint: hints} =
Enum.reduce(opts, %{derive: [], hint: []}, fn item, acc ->
get_derive = Keyword.get(item, :derive, [])
get_hint = Keyword.get(item, :hint, [])

Map.merge(acc, %{derive: acc.derive ++ [get_derive], hint: acc.hint ++ [get_hint]})
end)

acc ++ [Map.new([{:derive, derives}, {:field, field}, {:hint, hints}])]

acc ++ [Map.new([{:derive, get_derive}, {:field, field}, {:hint, get_hint}])]
_ ->
# We do not cover this setuation
acc
end

{field, values}, acc ->
%{derive: derives, hint: hints} =
Expand Down
11 changes: 10 additions & 1 deletion lib/macros/guarded_struct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2413,6 +2413,7 @@ defmodule GuardedStruct do
defp conditionals_fields_data_divider(builders) do
Enum.reduce(builders, %{data: [], errors: []}, fn
{field, conds, priority}, acc ->
# TODO: it just keeps one derive not list of them
%{data: data, errors: errors} =
{field, conds, acc, priority}
|> separate_conditions_based_priority()
Expand Down Expand Up @@ -2451,13 +2452,21 @@ defmodule GuardedStruct do
defp separate_conditions_based_priority({field, conds, acc, priority}, "normal") do
[success_data, error_data] = reduce_success_data_and_error_data(conds)

derives = Enum.map(success_data, fn {_data, derive} -> derive end)

data =
if(length(success_data) > 0,
do: [{field, {List.first(success_data) |> elem(0), derives}}],
else: []
)

Map.merge(acc, %{
errors:
if(length(error_data) > 0 and length(success_data) == 0,
do: [{field, if(priority, do: [List.first(error_data)], else: error_data)}],
else: []
),
data: if(length(success_data) > 0, do: [{field, List.first(success_data)}], else: [])
data: data
})
end

Expand Down
142 changes: 135 additions & 7 deletions test/guarded_struct_test/core_keys_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,15 @@ defmodule MishkaDeveloperToolsTest.GuardedStruct.CoreKeysTest do
end

conditional_field(:id, String.t(), auto: {Ecto.UUID, :generate}) do
field(:id, any(), derive: "sanitize(tag=strip_tags) validate(not_empty_string, uuid)")
field(:id, String.t(), derive: "sanitize(tag=strip_tags) validate(url, max_len=160)")
field(:id, String.t(),
derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
hint: "url_id"
)

field(:id, any(),
derive: "sanitize(tag=strip_tags) validate(not_empty_string, uuid)",
hint: "uuid_id"
)
end
end

Expand Down Expand Up @@ -852,10 +859,131 @@ defmodule MishkaDeveloperToolsTest.GuardedStruct.CoreKeysTest do
end

test "call auto core key top of a conditional fields" do
AllowedParentCustomDomain.builder(%{
username: "mishka",
auth: %{action: "ok"}
})
|> IO.inspect()
{:ok,
%__MODULE__.AllowedParentCustomDomain{
id: uuid,
auth: %__MODULE__.AllowedParentCustomDomain.Auth{
action: "ok"
},
username: "mishka"
}} =
assert AllowedParentCustomDomain.builder(
{:root,
%{
username: "mishka",
auth: %{action: "ok"}
}, :edit}
)

{:ok,
%__MODULE__.AllowedParentCustomDomain{
id: uuid1,
auth: %__MODULE__.AllowedParentCustomDomain.Auth{
action: "ok"
},
username: "mishka"
}} =
assert AllowedParentCustomDomain.builder(%{
username: "mishka",
auth: %{action: "ok"}
})

{:ok, _uuid} = assert Ecto.UUID.cast(uuid)
{:ok, _uuid} = assert Ecto.UUID.cast(uuid1)

# TODO: check the error of :edit
{:ok,
%MishkaDeveloperToolsTest.GuardedStruct.CoreKeysTest.AllowedParentCustomDomain{
id: "https://github.com/mishka-group/mishka_developer_tools",
auth: %MishkaDeveloperToolsTest.GuardedStruct.CoreKeysTest.AllowedParentCustomDomain.Auth{
action: "ok"
},
username: "mishka"
}} =
assert AllowedParentCustomDomain.builder(
{:root,
%{
username: "mishka",
auth: %{action: "ok"},
id: "https://github.com/mishka-group/mishka_developer_tools"
}, :edit}
)

{:ok,
%MishkaDeveloperToolsTest.GuardedStruct.CoreKeysTest.AllowedParentCustomDomain{
id: "9154b00d-4602-45c2-9562-46a2dcef257f",
auth: %MishkaDeveloperToolsTest.GuardedStruct.CoreKeysTest.AllowedParentCustomDomain.Auth{
action: "ok"
},
username: "mishka"
}} =
assert AllowedParentCustomDomain.builder(
{:root,
%{
username: "mishka",
auth: %{action: "ok"},
id: "9154b00d-4602-45c2-9562-46a2dcef257f"
}, :edit}
)

{:error, :bad_parameters,
[
%{
message: "Unexpected type error in id field",
field: :id,
action: :type,
__hint__: "url_id"
},
%{
message: "Invalid url format in the id field",
field: :id,
action: :url,
__hint__: "url_id"
},
%{
message: "Invalid UUID format in the id field",
field: :id,
action: :uuid,
__hint__: "uuid_id"
},
%{
message: "Invalid format in the id field",
field: :id,
action: :not_empty_string,
__hint__: "uuid_id"
}
]} =
assert AllowedParentCustomDomain.builder(
{:root,
%{
username: "mishka",
auth: %{action: "ok"},
id: :test
}, :edit}
)

{:error, :bad_parameters,
[
%{
message: "Is missing a url scheme (e.g. https) in the id field",
field: :id,
action: :url,
__hint__: "url_id"
},
%{
message: "Invalid UUID format in the id field",
field: :id,
action: :uuid,
__hint__: "uuid_id"
}
]} =
assert AllowedParentCustomDomain.builder(
{:root,
%{
username: "mishka",
auth: %{action: "ok"},
id: ":test"
}, :edit}
)
end
end

0 comments on commit 0b60bf2

Please sign in to comment.