Skip to content

Commit

Permalink
Split Command validations (#7)
Browse files Browse the repository at this point in the history
* move mix and escript examples module to an external dir

* add supported types config

* accumulate commands attr

* improve cmd parsing and create exceptions
  • Loading branch information
Zoey de Souza Pessanha committed Aug 26, 2023
1 parent 3436bd9 commit ae5c7fe
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 37 deletions.
3 changes: 3 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Config

config :nexus, supported_types: ~w(string atom integer float)a
File renamed without changes.
File renamed without changes.
24 changes: 13 additions & 11 deletions lib/nexus.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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)} - ")}
"""
Expand All @@ -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
34 changes: 8 additions & 26 deletions lib/nexus/command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions lib/nexus/command/missing_type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Nexus.Command.MissingType do
defexception message: "Command type is missing"
end
8 changes: 8 additions & 0 deletions lib/nexus/command/not_supported_type.ex
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions lib/nexus/command/validation.ex
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ae5c7fe

Please sign in to comment.