Elixir:十进制转换,不允许负数



你好,我收到一个值,这个值不能是负的,只能是正的,如果它是负的,我想返回一个自定义错误,如果它是负的,继续到管道。

我现在有这个:

def call(%{"id" => id, "value" => value}, operation) do
Multi.new()
|> Multi.run(:account, fn repo, _changes -> get_account(repo, id) end)
|> Multi.run(:update_balance, fn repo, %{account: account} ->
update_balance(repo, account, value, operation)
end)
end
defp update_balance(repo, account, value, operation) do
account
|> operation(value, operation)
|> update_account(repo, account)
end
defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> handle_cast(balance, operation)
end
defp handle_cast({:ok, value}, balance, :deposit), do: Decimal.add(balance, value)
defp handle_cast({:ok, value}, balance, :withdraw), do: Decimal.sub(balance, value)
defp handle_cast(:error, _balance, _operation), do: {:error, "Invalid operation!"}

虽然@sbacarob的答案是完全正确的,但为了方便起见,我还是把这个贴出来。

Decimal是一个普通结构体,因此可以使用它的内部结构来处理带有保护的负结构。这将适用于Elixir≥1.11和OTP≥23的现代版本。

defguard is_decimal_positive(value)
when is_map(value) and
value.__struct__ == Decimal and 
value.sign == 1

参见Kernel.defguard/1。并使用

defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> handle_cast(balance, operation)
end
defp handle_cast({:ok, value}, balance, _) 
when is_decimal_positive(value),
do: Decimal.add(balance, value)
defp handle_cast(_error, _balance, _),
do: {:error, "Number must be positive decimal"}

正如我在上一条评论中提到的,Decimal是一个不同的"类型",它大于整数。

幸运的是,Decimal提供了gt?,lt?compare函数,您可以使用它们将Decimals与整数进行比较。为了尽可能少地修改代码,一个比较干净的解决方案是在handle_cast之前添加一个小的额外函数,并为handle_cast添加另一个定义。像这样:

defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> maybe_positive?()
|> handle_cast(balance, operation)
end
defp maybe_positive?({:ok, value}) do
case Decimal.compare?(value, 0) do
:lt -> {:error, :negative_number}
:gt -> {:ok, value}
end
end
defp maybe_positive?(error), do: error
defp handle_cast({:error, :negative_number}, _balance, _operation) do
{:error, "Number must be positive"}
end
defp handle_cast({:ok, value}, balance, :deposit), do: Decimal.add(balance, value)
...

这样,如果它是负数,它将从handle_cast中通过{:error, :negative_number}的情况,如果它是正数或cast由于某种原因失败,它将通过您已经拥有的其他handle_cast验证