lib/mix/tasks/oas.gen.plug.ex

defmodule Mix.Tasks.Oas.Gen.Plug do
  @shortdoc "Generates Router and Plug modules from OpenAPI Specification"

  @moduledoc """
  Generates router and plug handlers for a proper OpenAPI Specification in YAML or JSON file.

      mix oas.gen.plug --file path/to/openapi.yaml
      mix oas.gen.plug --file path/to/openapi.yml
      mix oas.gen.plug --file path/to/openapi.json

  The arguments of `oas.gen.plug` mix task:

  * `--file`, required, the completed path to the specification file in YAML or JSON format.
  * `--router`, optional, the generated router's module alias, by default it is `Router` (the full module name is `Oasis.Gen.Router` by default), for example we set `--router Hello.MyRouter` meanwhile there is no other special name space defined, the final router module is `Oasis.Gen.Hello.MyRouter` in `/lib/oasis/gen/hello/my_router.ex` path.
  * `--name-space`, optional, the generated all modules' name space, by default it is `Oasis.Gen`, this argument will always override the name space from the input `--file` if any `"x-oasis-name-space"` field(s) defined.
  * `--force`, optional, forces creation without a shell prompt.
  * `--quiet`, optional, does not log command output.
  """
  use Mix.Task

  @parse_opts [
    switches: [
      file: :string,
      router: :string,
      name_space: :string,
      body_reader: :string,
      force: :boolean,
      quiet: :boolean
    ],
    aliases: [f: :file, r: :router, n: :name_space, q: :quiet]
  ]

  def run(args) do
    if Mix.Project.umbrella?() do
      Mix.raise("mix oas.gen.plug can only be run inside an application directory")
    end

    Mix.shell().info("Generates Router and Plug modules from OAS")

    paths = Mix.Oasis.generator_paths()

    opts = parse_opts(args)
    files = build(opts)
    Mix.Oasis.copy_from(paths, "priv/templates/oas.gen.plug", files, opts)
  end

  defp build(opts) do
    %ExJsonSchema.Schema.Root{schema: schema} = Oasis.Spec.read(opts[:file])

    opts = Keyword.take(opts, [:router, :name_space, :body_reader])

    Mix.Oasis.new(schema, opts)
  end

  defp parse_opts(args) do
    {opts, _, _} = OptionParser.parse(args, @parse_opts)
    validate_opts!(opts)
  end

  defp validate_opts!(opts) do
    oas_file_path = Keyword.get(opts, :file)

    if oas_file_path == nil do
      raise_with_help("Expected input `--file` option to be a valid path to a yaml/json file")
    else
      if File.exists?(oas_file_path) do
        opts
      else
        raise_with_help("Could not find the file in `#{oas_file_path}` path")
      end
    end
  end

  defp raise_with_help(msg) do
    Mix.raise("""
    #{msg}

    For example:

        mix oas.gen.plug --file path/to/file.yaml
        mix oas.gen.plug --file path/to/file.json
        mix oas.gen.plug --file path/to/file.yml --name-space Test --router Router

    By default the `--name-space` is "Oasis.Gen", and by default
    the `--router` is "Router", these two options are optional.
    """)
  end
end