Packages
ash_authentication
3.2.1
5.0.0-rc.12
5.0.0-rc.11
5.0.0-rc.10
5.0.0-rc.9
5.0.0-rc.8
5.0.0-rc.7
5.0.0-rc.6
5.0.0-rc.5
5.0.0-rc.4
5.0.0-rc.3
5.0.0-rc.2
5.0.0-rc.1
5.0.0-rc.0
4.14.1
4.14.0
4.13.7
4.13.6
4.13.5
4.13.4
4.13.3
4.13.2
retired
4.13.1
retired
4.13.0
4.12.0
4.11.0
4.10.0
4.9.9
4.9.8
4.9.7
4.9.6
4.9.5
4.9.4
4.9.3
4.9.2
4.9.1
4.9.0
4.8.7
4.8.6
4.8.5
4.8.3
4.8.2
4.8.1
4.8.0
4.7.6
4.7.5
4.7.4
4.7.3
4.7.2
4.7.1
4.7.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.6
4.5.5
4.5.4
4.5.3
4.5.2
4.5.1
4.5.0
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.12
4.3.11
4.3.10
4.3.9
4.3.8
4.3.7
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
4.0.0-rc.6
4.0.0-rc.5
4.0.0-rc.3
4.0.0-rc.2
4.0.0-rc.1
4.0.0-rc.0
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.0
3.7.9
3.7.8
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
3.6.1
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.3.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.0
3.0.3
Authentication extension for the Ash Framework.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash_authentication/info_generator.ex
defmodule AshAuthentication.InfoGenerator do
@moduledoc """
Used to dynamically generate configuration functions for Spark extensions
based on their DSL.
## Usage
```elixir
defmodule MyConfig do
use AshAuthentication.InfoGenerator, extension: MyDslExtension, sections: [:my_section]
end
```
"""
@type options :: [{:extension, module} | {:sections, [atom]}]
@doc false
@spec __using__(options) :: Macro.t()
defmacro __using__(opts) do
extension = Keyword.fetch!(opts, :extension) |> Macro.expand(__CALLER__)
sections = Keyword.get(opts, :sections, [])
quote do
require AshAuthentication.InfoGenerator
require unquote(extension)
AshAuthentication.InfoGenerator.generate_config_functions(
unquote(extension),
unquote(sections)
)
AshAuthentication.InfoGenerator.generate_options_functions(
unquote(extension),
unquote(sections)
)
AshAuthentication.InfoGenerator.generate_entity_functions(
unquote(extension),
unquote(sections)
)
end
end
@doc """
Given an extension and a list of DSL sections, generate an options function
which returns a map of all configured options for a resource (including
defaults).
"""
@spec generate_options_functions(module, [atom]) :: Macro.t()
defmacro generate_options_functions(extension, sections) do
for {path, options} <- extension_sections_to_option_list(extension, sections) do
function_name = :"#{Enum.join(path, "_")}_options"
quote location: :keep do
@doc """
#{unquote(Enum.join(path, "."))} DSL options
Returns a map containing the and any configured or default values.
"""
@spec unquote(function_name)(dsl_or_resource :: module | map) :: %{required(atom) => any}
def unquote(function_name)(dsl_or_resource) do
import Spark.Dsl.Extension, only: [get_opt: 4]
unquote(Macro.escape(options))
|> Stream.map(fn option ->
value =
dsl_or_resource
|> get_opt(option.path, option.name, Map.get(option, :default))
{option.name, value}
end)
|> Stream.reject(&is_nil(elem(&1, 1)))
|> Map.new()
end
end
end
end
@doc """
Given an extension and a list of DSL sections, generate an entities function
which returns a list of entities.
"""
@spec generate_entity_functions(module, [atom]) :: Macro.t()
defmacro generate_entity_functions(extension, sections) do
entity_paths =
extension.sections()
|> Stream.filter(&(&1.name in sections))
|> Stream.flat_map(&explode_section([], &1))
|> Stream.filter(fn {_, section} -> Enum.any?(section.entities) end)
|> Stream.map(&elem(&1, 0))
for path <- entity_paths do
function_name = path |> Enum.join("_") |> String.to_atom()
quote location: :keep do
@doc """
#{unquote(Enum.join(path, "."))} DSL entities
"""
@spec unquote(function_name)(dsl_or_resource :: module | map) :: [struct]
def unquote(function_name)(dsl_or_resource) do
import Spark.Dsl.Extension, only: [get_entities: 2]
get_entities(dsl_or_resource, unquote(path))
end
end
end
end
@doc """
Given an extension and a list of DSL sections generate individual config
functions for each option.
"""
@spec generate_config_functions(module, [atom]) :: Macro.t()
defmacro generate_config_functions(extension, sections) do
for {_, options} <- extension_sections_to_option_list(extension, sections) do
for option <- options do
generate_config_function(option)
end
end
end
defp explode_section(path, %{sections: [], name: name} = section),
do: [{path ++ [name], section}]
defp explode_section(path, %{sections: sections, name: name} = section) do
path = path ++ [name]
head = [{path, section}]
tail = Stream.flat_map(sections, &explode_section(path, &1))
Stream.concat(head, tail)
end
defp extension_sections_to_option_list(extension, sections) do
extension.sections()
|> Stream.filter(&(&1.name in sections))
|> Stream.flat_map(&explode_section([], &1))
|> Stream.reject(fn {_, section} -> Enum.empty?(section.schema) end)
|> Stream.map(fn {path, section} ->
schema =
section.schema
|> Enum.map(fn {name, opts} ->
opts
|> Map.new()
|> Map.take(~w[type doc default]a)
|> Map.update!(:type, &spec_for_type/1)
|> Map.put(:pred?, name |> to_string() |> String.ends_with?("?"))
|> Map.put(:name, name)
|> Map.put(:path, path)
|> Map.put(
:function_name,
path
|> Enum.concat([name])
|> Enum.join("_")
|> String.trim_trailing("?")
|> String.to_atom()
)
end)
{path, schema}
end)
|> Map.new()
end
defp generate_config_function(%{pred?: true} = option) do
function_name = :"#{option.function_name}?"
quote location: :keep do
@doc unquote(option.doc)
@spec unquote(function_name)(dsl_or_resource :: module | map) ::
unquote(option.type)
def unquote(function_name)(dsl_or_resource) do
import Spark.Dsl.Extension, only: [get_opt: 4]
get_opt(
dsl_or_resource,
unquote(option.path),
unquote(option.name),
unquote(option.default)
)
end
end
end
defp generate_config_function(option) do
quote location: :keep do
@doc unquote(Map.get(option, :doc, false))
@spec unquote(option.function_name)(dsl_or_resource :: module | map) ::
{:ok, unquote(option.type)} | :error
def unquote(option.function_name)(dsl_or_resource) do
import Spark.Dsl.Extension, only: [get_opt: 4]
case get_opt(
dsl_or_resource,
unquote(option.path),
unquote(option.name),
unquote(Map.get(option, :default, :error))
) do
:error -> :error
value -> {:ok, value}
end
end
@doc unquote(Map.get(option, :doc, false))
@spec unquote(:"#{option.function_name}!")(dsl_or_resource :: module | map) ::
unquote(option.type) | no_return
def unquote(:"#{option.function_name}!")(dsl_or_resource) do
import Spark.Dsl.Extension, only: [get_opt: 4]
case get_opt(
dsl_or_resource,
unquote(option.path),
unquote(option.name),
unquote(Map.get(option, :default, :error))
) do
:error ->
raise "No configuration for `#{unquote(option.name)}` present on `#{inspect(dsl_or_resource)}`."
value ->
value
end
end
end
end
defp spec_for_type({:behaviour, _module}), do: {:module, [], Elixir}
defp spec_for_type({:spark_function_behaviour, behaviour, {_, arity}}),
do:
spec_for_type(
{:or,
[
{:behaviour, behaviour},
{{:behaviour, behaviour}, {:keyword, [], Elixir}},
{:fun, arity}
]}
)
defp spec_for_type({:fun, arity}) do
args =
0..(arity - 1)
|> Enum.map(fn _ -> {:any, [], Elixir} end)
[{:->, [], [args, {:any, [], Elixir}]}]
end
defp spec_for_type({:or, [type]}), do: spec_for_type(type)
defp spec_for_type({:or, [next | remaining]}),
do: {:|, [], [spec_for_type(next), spec_for_type({:or, remaining})]}
defp spec_for_type({:in, %Range{first: first, last: last}})
when is_integer(first) and is_integer(last),
do: {:.., [], [first, last]}
defp spec_for_type({:in, %Range{first: first, last: last}}),
do:
{{:., [], [{:__aliases__, [], [:Range]}, :t]}, [],
[spec_for_type(first), spec_for_type(last)]}
defp spec_for_type({:in, [type]}), do: spec_for_type(type)
defp spec_for_type({:in, [next | remaining]}),
do: {:|, [], [spec_for_type(next), spec_for_type({:in, remaining})]}
defp spec_for_type({:list, subtype}), do: [spec_for_type(subtype)]
defp spec_for_type({:custom, _, _, _}), do: spec_for_type(:any)
defp spec_for_type({:tuple, subtypes}) do
subtypes
|> Enum.map(&spec_for_type/1)
|> List.to_tuple()
end
defp spec_for_type(:string),
do: {{:., [], [{:__aliases__, [alias: false], [:String]}, :t]}, [], []}
defp spec_for_type(terminal)
when terminal in ~w[any map atom string boolean integer non_neg_integer pos_integer float timeout pid reference mfa]a,
do: {terminal, [], Elixir}
defp spec_for_type(atom) when is_atom(atom), do: atom
defp spec_for_type(number) when is_number(number), do: number
defp spec_for_type(string) when is_binary(string), do: spec_for_type(:string)
defp spec_for_type({mod, arg}) when is_atom(mod) and is_list(arg),
do: {{:module, [], Elixir}, {:list, [], Elixir}}
defp spec_for_type(tuple) when is_tuple(tuple),
do: tuple |> Tuple.to_list() |> Enum.map(&spec_for_type/1) |> List.to_tuple()
defp spec_for_type([]), do: []
defp spec_for_type([type]), do: [spec_for_type(type)]
end