diff --git a/lib/nimble_options.ex b/lib/nimble_options.ex index 3916edd..7733518 100644 --- a/lib/nimble_options.ex +++ b/lib/nimble_options.ex @@ -60,6 +60,19 @@ defmodule NimbleOptions do if `type: :integer`, NimbleOptions will use `t:integer/0` as the auto-generated type doc. """ + ], + type_spec: [ + type_spec: :any, + doc: """ + The quoted spec to use *in the typespec* for the option item. You should use this + when the auto-generated spec is not specific enough. For example, if you are performing + custom validation on an option (with the `{:custom, ...}` type), then the + generated type spec for that option will always be `t:term/0`, but you can use + this option to customize that. The value for this option **must** be a quoted Elixir + term. For example, if you have an `:exception` option that is validated with a + `{:custom, ...}` type (based on `is_exception/1`), you can override the type + spec for that option to be `quote(do: Exception.t())`. *Available since v1.1.0*. + """ ] ] ] diff --git a/lib/nimble_options/docs.ex b/lib/nimble_options/docs.ex index 7cdf411..8361e1d 100644 --- a/lib/nimble_options/docs.ex +++ b/lib/nimble_options/docs.ex @@ -171,7 +171,9 @@ defmodule NimbleOptions.Docs do def schema_to_spec(schema) do schema |> Enum.map(fn {key, opt_schema} -> - typespec = type_to_spec(opt_schema[:type]) + typespec = + Keyword.get_lazy(opt_schema, :type_spec, fn -> type_to_spec(opt_schema[:type]) end) + quote do: {unquote(key), unquote(typespec)} end) |> unionize_quoted() diff --git a/test/nimble_options_test.exs b/test/nimble_options_test.exs index d4829df..6850b01 100644 --- a/test/nimble_options_test.exs +++ b/test/nimble_options_test.exs @@ -72,7 +72,7 @@ defmodule NimbleOptionsTest do Reason: \ unknown options [:unknown_schema_option], \ valid options are: [:type, :required, :default, :keys, \ - :deprecated, :doc, :subsection, :type_doc] \ + :deprecated, :doc, :subsection, :type_doc, :type_spec] \ (in options [:producers, :keys, :*, :keys, :module])\ """ @@ -1799,6 +1799,18 @@ defmodule NimbleOptionsTest do end) end + test "supports overriding specific specs with the :type_spec schema option" do + schema = [ + name: [ + type: :any, + type_spec: quote(do: atom()) + ] + ] + + assert NimbleOptions.option_typespec(schema) == + quote(do: {:name, atom()}) + end + # TODO: remove check when we depend on Elixir 1.12+ if Version.match?(System.version(), "~> 1.12") do test "ranges with step" do