lib/openapi/openapispec.ex

defmodule ArkeServer.ApiSpec do
  alias OpenApiSpex.{
    Components,
    Info,
    OpenApi,
    Paths,
    SecurityScheme,
    Parameter,
    Schema,
    Server
  }

  alias ArkeServer.{Endpoint, Router}
  @behaviour OpenApi

  @impl OpenApi
  def spec do
    %OpenApi{
      servers: get_servers(Application.get_env(:arke_server, :endpoint_module)),
      info: %Info{
        title: "Arke Api",
        version: Mix.Project.config()[:version]
      },
      components: %Components{
        securitySchemes: %{
          "authorization" => %SecurityScheme{
            type: "http",
            scheme: "bearer",
            description:
              "API Token must follow: `Bearer {access_token}` Use the signin endpoint to get the token value"
          }
        },
        parameters: get_parameters()
      },
      # Populate the paths from a phoenix router
      paths: Paths.from_router(Router)
    }
    # Discover request/response schemas from path specs
    |> OpenApiSpex.resolve_schema_modules()
  end

  defp get_parameters() do
    %{
      "arke-project-key" => %Parameter{
        name: "arke-project-key",
        in: :header,
        required: true,
        description: "Key which define the project where to use the API",
        schema: %Schema{
          type: :string
        }
      },
      "unit_id" => %Parameter{
        name: "unit_id",
        in: :path,
        required: true,
        description: "Unit ID",
        schema: %Schema{
          type: :string
        }
      },
      "group_id" => %Parameter{
        name: "group_id",
        in: :path,
        required: true,
        description: "Group ID",
        schema: %Schema{
          type: :string
        }
      },
      "link_id" => %Parameter{
        name: "link_id",
        in: :path,
        required: true,
        description: "Link ID",
        schema: %Schema{
          type: :string
        }
      },
      "arke_parameter_id" => %Parameter{
        name: "arke_parameter_id",
        in: :path,
        required: true,
        description: "Id of the parameter to link",
        schema: %Schema{
          type: :string
        }
      },
      "arke_id" => %Parameter{
        name: "arke_id",
        in: :path,
        required: true,
        description: "Arke ID",
        schema: %Schema{type: :string}
      },
      "limit" => %Parameter{
        name: "limit",
        in: :query,
        required: false,
        description: "Limits the number of returned results",
        schema: %Schema{
          type: :integer,
          minimum: 0
        }
      },
      "offset" => %Parameter{
        name: "offset",
        in: :query,
        required: false,
        description: "Set an offset",
        schema: %Schema{
          type: :integer,
          minimum: 0
        }
      },
      "order" => %Parameter{
        name: "order[]",
        in: :query,
        required: false,
        description: "Define in which order get the returned results",
        schema: %Schema{
          type: :string,
          example: "order[]=id;desc&order[]=label;asc"
        }
      },
      "filter" => %Parameter{
        name: "filter",
        in: :query,
        required: false,
        description: "Arke API filter",
        schema: %Schema{
          type: :string,
          example: "filter=and(gte(age,23),contains(name,string))"
        }
      }
    }
  end

  defp get_servers(nil) do
    [%Server{url: "http://localhost:4000"}]
  end

  defp get_servers(endpoint_module) when is_list(endpoint_module),
    do: create_server_list(endpoint_module)

  defp get_servers(endpoint_module), do: create_server_list([endpoint_module])

  defp create_server_list(module_list) do
    server_list = Enum.into(module_list, [], fn m -> get_uri(m) end)
    server_list = Enum.filter(server_list, fn s -> !is_nil(s) end)

    case length(server_list) do
      0 -> get_servers(nil)
      _ -> server_list
    end
  end

  defp get_uri(module) do
    with true <- Kernel.function_exported?(module, :struct_url, 0),
         true <- Kernel.function_exported?(module, :path, 1) do
      url = module.struct_url()
      path = module.path("") || "/"
      uri = %{url | path: path}
      %Server{url: URI.to_string(uri)}
    else
      _ -> nil
    end
  end
end