defmodule AccessGrid.Params do
@moduledoc """
Helpers for composing SDK request params. Currently just `require/2` for
client-side required-field presence checks — used internally by `Console`
and `AccessPasses` create/action functions to surface "you forgot X" without
a round-trip to the API.
"""
@doc """
Returns `:ok` if every atom key in `required_keys` has a non-blank value
in `params`. Returns `{:error, :missing_required, missing}` where `missing`
is the non-empty list of every missing/blank key (in input order —
deterministic).
"Blank" means `nil`, key absent, an empty string `""`, or a whitespace-only
string (`" "`, `"\\t\\n"`, etc.). Empty lists, maps, and other values pass
through — the server validates whether an empty collection is acceptable
for a given field (e.g. the server rejects `subscribed_events: []` with its own
clear 422 message).
Atom-keyed maps only; mirrors the SDK's input convention.
## Examples
iex> AccessGrid.Params.require(%{name: "X", protocol: "desfire"}, [:name, :protocol])
:ok
iex> AccessGrid.Params.require(%{name: "X"}, [:name, :protocol])
{:error, :missing_required, [:protocol]}
iex> AccessGrid.Params.require(%{}, [:name, :platform, :protocol])
{:error, :missing_required, [:name, :platform, :protocol]}
iex> AccessGrid.Params.require(%{name: " "}, [:name])
{:error, :missing_required, [:name]}
"""
@spec require(map(), [atom()]) ::
:ok | {:error, :missing_required, [atom(), ...]}
def require(params, required_keys) when is_map(params) and is_list(required_keys) do
case Enum.filter(required_keys, &blank?(Map.get(params, &1))) do
[] -> :ok
missing -> {:error, :missing_required, missing}
end
end
@doc """
Returns `:ok` if `value` is non-blank. Returns
`{:error, :missing_required, [name]}` otherwise (single-element list for
shape consistency with `require/2`). Used to validate positional arguments
(path-segment IDs like `template_id`, `card_id`, etc.) where the SDK
function takes a positional string rather than a key in a params map.
## Examples
iex> AccessGrid.Params.require_present("tpl_abc", :template_id)
:ok
iex> AccessGrid.Params.require_present(nil, :template_id)
{:error, :missing_required, [:template_id]}
iex> AccessGrid.Params.require_present(" ", :template_id)
{:error, :missing_required, [:template_id]}
"""
@spec require_present(any(), atom()) ::
:ok | {:error, :missing_required, [atom(), ...]}
def require_present(value, name) when is_atom(name) do
if blank?(value), do: {:error, :missing_required, [name]}, else: :ok
end
defp blank?(nil), do: true
defp blank?(val) when is_binary(val), do: String.trim(val) == ""
defp blank?(_), do: false
end