From ae5c7fe20e7f757e5096da614c55ca24d5a17bb5 Mon Sep 17 00:00:00 2001 From: Zoey de Souza Pessanha Date: Sat, 26 Aug 2023 18:28:35 -0300 Subject: [PATCH] Split Command validations (#7) * move mix and escript examples module to an external dir * add supported types config * accumulate commands attr * improve cmd parsing and create exceptions --- config/config.exs | 3 +++ {lib => examples}/escript/example.ex | 0 {lib => examples}/mix/tasks/example.ex | 0 lib/nexus.ex | 24 +++++++++-------- lib/nexus/command.ex | 34 ++++++------------------- lib/nexus/command/missing_type.ex | 3 +++ lib/nexus/command/not_supported_type.ex | 8 ++++++ lib/nexus/command/validation.ex | 30 ++++++++++++++++++++++ 8 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 config/config.exs rename {lib => examples}/escript/example.ex (100%) rename {lib => examples}/mix/tasks/example.ex (100%) create mode 100644 lib/nexus/command/missing_type.ex create mode 100644 lib/nexus/command/not_supported_type.ex create mode 100644 lib/nexus/command/validation.ex diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..ad803da --- /dev/null +++ b/config/config.exs @@ -0,0 +1,3 @@ +import Config + +config :nexus, supported_types: ~w(string atom integer float)a diff --git a/lib/escript/example.ex b/examples/escript/example.ex similarity index 100% rename from lib/escript/example.ex rename to examples/escript/example.ex diff --git a/lib/mix/tasks/example.ex b/examples/mix/tasks/example.ex similarity index 100% rename from lib/mix/tasks/example.ex rename to examples/mix/tasks/example.ex diff --git a/lib/nexus.ex b/lib/nexus.ex index 69912e4..fdb15fb 100644 --- a/lib/nexus.ex +++ b/lib/nexus.ex @@ -36,7 +36,7 @@ defmodule Nexus do defmacro __using__(_opts) do quote do - @commands %{} + Module.register_attribute(__MODULE__, :commands, accumulate: true) import Nexus, only: [defcommand: 2] require Nexus @@ -57,12 +57,7 @@ defmodule Nexus do @spec defcommand(atom, keyword) :: Macro.t() defmacro defcommand(cmd, opts) do quote do - command = - unquote(opts) - |> Keyword.put(:module, __MODULE__) - |> Nexus.Command.parse!() - - @commands Map.put(@commands, unquote(cmd), command) + @commands Nexus.__make_command__!(__MODULE__, unquote(cmd), unquote(opts)) end end @@ -120,7 +115,7 @@ defmodule Nexus do def __commands__, do: @commands def run([name | args]) do - cmd = Enum.find(@commands, fn {cmd, _spec} -> to_string(cmd) == name end) + cmd = Enum.find(@commands, fn cmd -> to_string(cmd.name) == name end) Nexus.CommandDispatcher.dispatch!(cmd, args) end @@ -142,12 +137,11 @@ defmodule Nexus do banner = if function_exported?(cli_module, :banner, 0) do - "#{cli_module.banner()}" + "#{cli_module.banner()}\n\n" end """ - #{banner}\n - + #{banner} COMMANDS:\n #{Enum.map_join(cmds, "\n", &" #{elem(&1, 0)} - ")} """ @@ -168,4 +162,12 @@ defmodule Nexus do def parse_to(:float, value) do String.to_float(value) end + + def __make_command__!(module, cmd_name, opts) do + opts + |> Keyword.put(:name, cmd_name) + |> Keyword.put(:module, module) + |> Keyword.put_new(:required?, false) + |> Nexus.Command.parse!() + end end diff --git a/lib/nexus/command.ex b/lib/nexus/command.ex index ebf0426..9436a27 100644 --- a/lib/nexus/command.ex +++ b/lib/nexus/command.ex @@ -4,37 +4,19 @@ defmodule Nexus.Command do implements some basic validations. """ - require Logger + import Nexus.Command.Validation - @type t :: %Nexus.Command{module: atom, type: String.t(), required?: boolean} + @type t :: %Nexus.Command{module: atom, type: String.t(), required?: boolean, name: atom} - @enforce_keys ~w(module type)a - defstruct module: nil, required?: true, type: nil + @enforce_keys ~w(module type name)a + defstruct module: nil, required?: true, type: nil, name: nil - @spec parse!(keyword | map) :: Nexus.Command.t() + @spec parse!(keyword) :: Nexus.Command.t() def parse!(attrs) do attrs - |> maybe_convert_to_map() - |> validate_field(:type) + |> Map.new() + |> validate_type() + |> validate_name() |> then(&struct(__MODULE__, &1)) end - - defp maybe_convert_to_map(kw) when is_list(kw) do - Map.new(kw) - end - - defp maybe_convert_to_map(map), do: map - - defp validate_field(%{type: type} = attrs, :type) do - unless valid_type?(type) do - raise "Invalid command type" - end - - attrs - end - - defp validate_field(_, _), do: raise("Invalid command param") - - defp valid_type?(:string), do: true - defp valid_type?(_), do: false end diff --git a/lib/nexus/command/missing_type.ex b/lib/nexus/command/missing_type.ex new file mode 100644 index 0000000..ceaabd6 --- /dev/null +++ b/lib/nexus/command/missing_type.ex @@ -0,0 +1,3 @@ +defmodule Nexus.Command.MissingType do + defexception message: "Command type is missing" +end diff --git a/lib/nexus/command/not_supported_type.ex b/lib/nexus/command/not_supported_type.ex new file mode 100644 index 0000000..cb68b4e --- /dev/null +++ b/lib/nexus/command/not_supported_type.ex @@ -0,0 +1,8 @@ +defmodule Nexus.Command.NotSupportedType do + defexception [:message] + + @impl true + def exception(type) do + %__MODULE__{message: "Command type not supported yet: #{inspect(type)}"} + end +end diff --git a/lib/nexus/command/validation.ex b/lib/nexus/command/validation.ex new file mode 100644 index 0000000..62b105a --- /dev/null +++ b/lib/nexus/command/validation.ex @@ -0,0 +1,30 @@ +defmodule Nexus.Command.Validation do + @moduledoc """ + Defines validations for a `Nexus.Command` struct + """ + + alias Nexus.Command.MissingType + alias Nexus.Command.NotSupportedType + + @supported_types Application.compile_env!(:nexus, :supported_types) + + @spec validate_type(map) :: map + def validate_type(%{type: type} = attrs) do + if type in @supported_types do + attrs + else + raise NotSupportedType, type + end + end + + def validate_type(_), do: raise(MissingType) + + @spec validate_name(map) :: map + def validate_name(%{name: name} = attrs) do + if is_atom(name) do + attrs + else + raise ArgumentError, "Command name must be an atom" + end + end +end