diff --git a/lib/helper/derive/derive.ex b/lib/helper/derive/derive.ex index d912d4d..0bfe886 100644 --- a/lib/helper/derive/derive.ex +++ b/lib/helper/derive/derive.ex @@ -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} -> @@ -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 @@ -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 = @@ -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} = diff --git a/lib/macros/guarded_struct.ex b/lib/macros/guarded_struct.ex index 482bcb7..edd6e31 100644 --- a/lib/macros/guarded_struct.ex +++ b/lib/macros/guarded_struct.ex @@ -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() @@ -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 diff --git a/test/guarded_struct_test/core_keys_test.exs b/test/guarded_struct_test/core_keys_test.exs index 624d23b..2a2140a 100644 --- a/test/guarded_struct_test/core_keys_test.exs +++ b/test/guarded_struct_test/core_keys_test.exs @@ -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 @@ -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