# SPDX-FileCopyrightText: 2019 ash contributors # # SPDX-License-Identifier: MIT defmodule Ash.Type.Integer do @constraints [ max: [ type: {:custom, __MODULE__, :integer, []}, doc: "Enforces a maximum on the value" ], min: [ type: {:custom, __MODULE__, :integer, []}, doc: "Enforces a minimum on the value" ] ] @moduledoc """ Represents a simple integer A builtin type that can be referenced via `:integer` ### Constraints #{Spark.Options.docs(@constraints)} """ use Ash.Type require Ash.Expr @impl true def matches_type?(v, _) do is_integer(v) end @impl true def cast_atomic(expr, _) do {:atomic, expr} end @impl true def apply_atomic_constraints(expr, constraints) do expr = case {constraints[:max], constraints[:min]} do {nil, nil} -> expr {max, nil} -> Ash.Expr.expr( if ^expr > ^max do error( Ash.Error.Changes.InvalidChanges, message: "must be less than or equal to %{max}", vars: %{max: ^max} ) else ^expr end ) {nil, min} -> Ash.Expr.expr( if ^expr < ^min do error( Ash.Error.Changes.InvalidChanges, message: "must be greater than or equal to %{min}", vars: %{min: ^min} ) else ^expr end ) {max, min} -> Ash.Expr.expr( cond do ^expr < ^min -> error( Ash.Error.Changes.InvalidChanges, message: "must be greater than or equal to %{min}", vars: %{min: ^min} ) ^expr > ^max -> error( Ash.Error.Changes.InvalidChanges, message: "must be less than or equal to %{max}", vars: %{max: ^max} ) true -> ^expr end ) end {:ok, expr} end @impl true def storage_type(_), do: :integer @impl true def generator(constraints) do min = constraints[:min] || -2_147_483_648 max = constraints[:max] || 2_147_483_647 StreamData.integer(min..max) end @impl true def constraints, do: @constraints @doc false def integer(value) when is_integer(value), do: {:ok, value} def integer(_), do: {:error, "must be an integer"} def apply_constraints(nil, _), do: :ok def apply_constraints(value, constraints) do errors = Enum.reduce(constraints, [], fn {:max, max}, errors -> if value > max do [[message: "must be less than or equal to %{max}", max: max] | errors] else errors end {:min, min}, errors -> if value < min do [[message: "must be more than or equal to %{min}", min: min] | errors] else errors end end) case errors do [] -> {:ok, value} errors -> {:error, errors} end end @impl true def cast_input(value, _) do Ecto.Type.cast(:integer, value) end @impl true def cast_stored(nil, _), do: {:ok, nil} def cast_stored(string, constraints) when is_binary(string) do case Integer.parse(string) do {integer, ""} -> cast_stored(integer, constraints) _ -> :error end end def cast_stored(value, _) do Ecto.Type.load(:integer, value) end @impl true def dump_to_native(nil, _), do: {:ok, nil} def dump_to_native(value, _) do Ecto.Type.dump(:integer, value) end end