diff --git a/lib/plug/redoc_ui.ex b/lib/plug/redoc_ui.ex
index b03b4cf..fd9ac6c 100644
--- a/lib/plug/redoc_ui.ex
+++ b/lib/plug/redoc_ui.ex
@@ -1,4 +1,29 @@
defmodule Redoc.Plug.RedocUI do
+ @plug_options_schema [
+ title: [
+ type: :string,
+ default: "ReDoc",
+ doc: "Custom ReDoc site title."
+ ],
+ spec_url: [
+ type: :string,
+ doc: "Open API spec URL.",
+ deprecated: "Use `:spec_url` in `:redoc_opts` instead."
+ ],
+ redoc_version: [
+ type: :string,
+ default: "latest",
+ doc: "Specify ReDoc version"
+ ],
+ redoc_options: [
+ type: :keyword_list,
+ keys: Redoc.options_schema(),
+ doc: """
+ ReDoc options. See https://github.com/Redocly/redoc#redoc-options-object for more details.
+ """
+ ]
+ ]
+
@moduledoc """
A Plug for rendering Redoc UI.
@@ -15,9 +40,7 @@ defmodule Redoc.Plug.RedocUI do
## Options
- * `spec_url` - The openapi path to fetch. Support both `yaml` and `json`.
- * `redoc_version` - Specify a Redoc version, default to `latest`.
- * `title` - The documentation HTML page title, default to `ReDoc`.
+ #{NimbleOptions.docs(@plug_options_schema)}
"""
@behaviour Plug
@@ -42,8 +65,8 @@ defmodule Redoc.Plug.RedocUI do
- <%= k %>="<%= v %>"
+ <%= for {k, v} <- redoc_options do %>
+ <%= k %>="<%= v %>"
<% end %>
>
@@ -51,55 +74,23 @@ defmodule Redoc.Plug.RedocUI do
"""
- @redoc_options [
- :disable_search,
- :download_definition_url,
- :download_file_name,
- :expand_default_server_variables,
- :expand_responses,
- :expand_single_schema_field,
- :generated_payload_samples_max_depth,
- :hide_download_button,
- :hide_fab,
- :hide_hostname,
- :hide_loading,
- :hide_schema_pattern,
- :hide_schema_titles,
- :hide_single_request_sample_tab,
- :json_sample_expand_level,
- :lazy_rendering,
- :max_displayed_enum_values,
- :menu_toggle,
- :min_character_length_to_init_search,
- :native_scrollbars,
- :nonce,
- :only_required_in_samples,
- :path_in_middle_panel,
- :payload_sample_idx,
- :required_props_first,
- :schema_expansion_level,
- :scroll_y_offset,
- :show_extensions,
- :show_object_schema_examples,
- :show_webhook_ver,
- :side_nav_style,
- :simple_one_of_type_label,
- :sort_enum_values_alphabetically,
- :sort_operations_alphabetically,
- :sort_props_alphabetically,
- :sort_tags_alphabetically,
- :spec_url,
- :theme,
- :untrusted_spec
- ]
-
@impl true
def init(opts) do
- redoc_opts = encode_options(opts)
- redoc_version = Keyword.get(opts, :redoc_version, "latest")
- title = Keyword.get(opts, :title, "ReDoc")
+ case NimbleOptions.validate(opts, @plug_options_schema) do
+ {:ok, opts} ->
+ # Honor spec_url in the root of the `opts`.
+ redoc_options =
+ (opts[:redoc_options] || [])
+ |> maybe_merge_spec_url(opts[:spec_url])
+ |> encode_options()
- [redoc_opts: redoc_opts, redoc_version: redoc_version, title: title]
+ opts
+ |> Keyword.delete(:redoc_options)
+ |> Keyword.put(:redoc_options, redoc_options)
+
+ otherwise ->
+ otherwise
+ end
end
@impl true
@@ -109,9 +100,14 @@ defmodule Redoc.Plug.RedocUI do
|> send_resp(200, EEx.eval_string(@index_html, opts))
end
- defp encode_options(opts) do
- Keyword.take(opts, @redoc_options)
- |> Enum.map(fn {key, value} -> {to_kebab_case(key), value} end)
+ defp maybe_merge_spec_url(redoc_options, nil), do: redoc_options
+
+ defp maybe_merge_spec_url(redoc_options, spec_url),
+ do: Keyword.merge(redoc_options, spec_url: spec_url)
+
+ # TODO: need test.
+ defp encode_options(redoc_options) do
+ Enum.map(redoc_options, fn {key, value} -> {to_kebab_case(key), value} end)
end
defp to_kebab_case(identifier) do
diff --git a/lib/redoc.ex b/lib/redoc.ex
index 1a70aa4..f58608f 100644
--- a/lib/redoc.ex
+++ b/lib/redoc.ex
@@ -5,4 +5,140 @@ defmodule Redoc do
Please consult documentation in `Redoc.Plug.RedocUI` for
more details.
"""
+
+ @doc """
+ A list of ReDoc options schema.
+ """
+ # NOTE: lazy_rendering doesn't add to this list since ReDoc documentation said it
+ # it doesn't implement it yet.
+ def options_schema() do
+ [
+ disable_search: [
+ type: :boolean,
+ default: false
+ ],
+ min_character_length_to_init_search: [
+ type: :integer,
+ default: 3
+ ],
+ expand_default_server_variables: [
+ type: :boolean,
+ default: false
+ ],
+ expand_responses: [
+ type: :string,
+ default: "all"
+ ],
+ generated_payload_samples_max_depth: [
+ type: :integer,
+ default: 10
+ ],
+ max_displayed_enum_values: [
+ type: :integer
+ ],
+ hide_download_button: [
+ type: :boolean,
+ default: true
+ ],
+ download_file_name: [
+ type: :string
+ ],
+ download_definition_url: [
+ type: :string
+ ],
+ hide_hostname: [
+ type: :boolean
+ ],
+ hide_loading: [
+ type: :boolean
+ ],
+ hide_fab: [
+ type: :boolean
+ ],
+ hide_schema_pattern: [
+ type: :boolean
+ ],
+ hide_single_request_sample_tab: [
+ type: :boolean
+ ],
+ show_object_schema_examples: [
+ type: :boolean,
+ default: false
+ ],
+ expand_single_schema_field: [
+ type: :boolean
+ ],
+ schema_expansion_level: [
+ type: {:or, [:integer, :string]},
+ default: 0
+ ],
+ json_sample_expand_level: [
+ type: {:or, [:integer, :string]},
+ default: 0
+ ],
+ hide_schema_titles: [
+ type: :boolean
+ ],
+ simple_one_of_type_label: [
+ type: :boolean
+ ],
+ sort_enum_values_alphabetically: [
+ type: :boolean
+ ],
+ sort_operations_alphabetically: [
+ type: :boolean
+ ],
+ sort_tags_alphabetically: [
+ type: :boolean
+ ],
+ menu_toggle: [
+ type: :boolean,
+ default: true
+ ],
+ native_scrollbars: [
+ type: :boolean
+ ],
+ only_required_in_samples: [
+ type: :boolean
+ ],
+ path_in_middle_panel: [
+ type: :boolean
+ ],
+ required_props_first: [
+ type: :boolean
+ ],
+ # NOTE: function not support.
+ scroll_y_offset: [
+ type: {:or, [:integer, :string]}
+ ],
+ show_extensions: [
+ type: {:or, [:integer, :string, {:list, :string}]}
+ ],
+ sort_props_alphabetically: [
+ type: :boolean
+ ],
+ payload_sample_idx: [
+ type: :integer
+ ],
+ # TODO: revisit theme object.
+ # theme: [
+ # type: :keyword_list
+ # ]
+ untrusted_spec: [
+ type: :boolean
+ ],
+ nonce: [
+ type: :string
+ ],
+ side_nav_style: [
+ type: {:in, [:"summary-only", :"path-only", :"id-only"]}
+ ],
+ show_webhook_ver: [
+ type: :boolean
+ ],
+ spec_url: [
+ type: :string
+ ]
+ ]
+ end
end
diff --git a/mix.exs b/mix.exs
index fdd1d22..246786e 100644
--- a/mix.exs
+++ b/mix.exs
@@ -25,6 +25,7 @@ defmodule Redoc.MixProject do
[
{:plug, "~> 1.0"},
{:jason, "~> 1.0"},
+ {:nimble_options, "~> 0.3"},
{:ex_doc, "~> 0.28", only: :dev, runtime: false},
]
end
diff --git a/mix.lock b/mix.lock
index 67d5079..16f0a80 100644
--- a/mix.lock
+++ b/mix.lock
@@ -6,6 +6,7 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
+ "nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"open_api_spex": {:hex, :open_api_spex, "3.11.0", "e4ea9c00e2891c3195c2df511c49e280e7210272ba0077d8d9975258c70e43c0", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "640bd09f6bdd96cc0264213bfa229b0f8b3868df3384311de57e0f3fa22fc3af"},
"plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},