Current section
Files
Jump to
Current section
Files
lib/open_api/generator/naming.ex
defmodule OpenAPI.Generator.Naming do
@moduledoc false
alias OpenAPI.Config
alias OpenAPI.Spec.Schema
alias OpenAPI.State
@type module_and_type :: {module :: String.t(), type :: atom}
@doc """
Returns the name of the schema before processing the generator configuration.
"""
@spec original_name(Schema.t()) :: module_and_type | nil
def original_name(schema) do
with {:ok, module_and_type} <- schema_to_module(schema) do
module_and_type
end
end
@doc """
Returns the name of the schema after processing the generator configuration.
"""
@spec referenced_name(State.t(), Schema.t(), keyword) :: module_and_type | nil
def referenced_name(%State{config: config} = state, schema, opts \\ []) do
%Config{group: group, ignore: ignore, merge: merge, rename: rename} = config
with {:ok, module_and_type} <- schema_to_module(schema),
merged_module_and_type <- process_merge_settings(module_and_type, merge),
merged_module_and_type <-
maybe_collapse_merged_schemas(
opts[:skip_collapse],
state,
schema,
merged_module_and_type
),
{:ok, non_ignored_module} <- process_ignore_settings(merged_module_and_type, ignore) do
non_ignored_module
|> process_rename_settings(rename)
|> process_group_settings(group)
end
end
@spec schema_to_module(Schema.t()) :: {:ok, module_and_type} | nil
defp schema_to_module(%Schema{"$oag_last_ref_path": ["components", "schemas", schema_name]}) do
module =
schema_name
|> String.replace("-", "_")
|> Macro.camelize()
{:ok, {module, :t}}
end
defp schema_to_module(_schema), do: nil
@spec process_merge_settings(module_and_type, Options.merge_options()) :: module_and_type
defp process_merge_settings({module, type}, merge_options) do
Enum.reduce(merge_options, {module, type}, fn
{before_merge, after_merge}, {module, type} ->
cond do
is_struct(before_merge, Regex) and Regex.match?(before_merge, module) ->
new_module = String.replace(module, before_merge, after_merge)
new_type = merged_type(module, new_module)
{new_module, new_type}
not is_struct(before_merge) and module == to_string(before_merge) ->
new_type = merged_type(before_merge, after_merge)
{after_merge, new_type}
:else ->
{module, type}
end
# {before_merge, after_merge, opts}, {name, merged?} ->
# if name == to_string(before_merge) do
# new_type =
# if new_type = opts[:type] do
# new_type
# else
# merged_type(before_merge, after_merge)
# end
# {{after_merge, new_type}, true}
# else
# {{module, type}, merged?}
# end
end)
end
@spec merged_type(String.t() | atom, String.t() | atom) :: atom
defp merged_type(before_merge, after_merge) do
cond do
String.starts_with?(before_merge, after_merge) ->
before_merge
|> String.trim_leading(after_merge)
|> Macro.underscore()
String.ends_with?(before_merge, after_merge) ->
before_merge
|> String.trim_trailing(after_merge)
|> Macro.underscore()
:else ->
Macro.underscore(before_merge)
end
|> String.to_atom()
end
@spec maybe_collapse_merged_schemas(boolean, State.t(), Schema.t(), module_and_type) ::
module_and_type
defp maybe_collapse_merged_schemas(true, _state, _schema, module_and_type) do
module_and_type
end
defp maybe_collapse_merged_schemas(_, state, schema, module_and_type) do
collapse_merged_schemas(state, schema, module_and_type)
end
@spec collapse_merged_schemas(State.t(), Schema.t(), module_and_type) :: module_and_type
defp collapse_merged_schemas(_state, _schema, {module, :t}), do: {module, :t}
defp collapse_merged_schemas(state, schema, {module, type}) do
destination_schema = state.schemas[module]
cond do
is_nil(destination_schema) ->
{module, type}
same?(schema, destination_schema) ->
{module, :t}
:else ->
{module, type}
end
end
@spec same?(Schema.t(), Schema.t()) :: boolean
defp same?(schema_one, schema_two) do
schema_one_fields = fields(schema_one)
schema_two_fields = fields(schema_two)
Map.equal?(schema_one_fields, schema_two_fields)
end
@spec process_rename_settings(module_and_type, Options.rename_options()) :: module_and_type
defp process_rename_settings({module, type}, replacements) do
Enum.reduce(replacements, {module, type}, fn {pattern, replacement}, {module, type} ->
{String.replace(module, pattern, replacement), type}
end)
end
@spec process_group_settings(module_and_type, [module]) :: module_and_type
defp process_group_settings({module, type}, groups) do
Enum.reduce(groups, {module, type}, fn group, {module, type} ->
group = inspect(group)
cond do
module == group ->
{module, type}
String.starts_with?(module, "#{group}.") ->
{module, type}
String.starts_with?(module, group) ->
{String.replace_leading(module, group, "#{group}."), type}
:else ->
{module, type}
end
end)
end
@spec process_ignore_settings(module_and_type, Options.ignore_options()) ::
{:ok, module_and_type} | nil
defp process_ignore_settings({module, type}, ignore_options) do
unless ignored?(module, ignore_options) do
{:ok, {module, type}}
end
end
@spec ignored?(String.t(), Options.ignore_options()) :: boolean
defp ignored?(schema_name, ignore_options) do
Enum.any?(ignore_options, fn
%Regex{} = regex -> Regex.match?(regex, schema_name)
^schema_name -> true
_ -> false
end)
end
#
# Duplicate Stuff
#
def fields(%Schema{properties: properties, required: required}) do
Enum.map(properties, fn
{field_name, %Schema{nullable: nullable?} = schema} ->
required? = is_list(required) and field_name in required
%{
name: field_name,
nullable: nullable?,
required: required?,
type: type(schema)
}
{field_name, spec} ->
required? = is_list(required) and field_name in required
%{
name: field_name,
nullable: false,
required: required?,
type: type(spec)
}
end)
|> Enum.map(fn %{name: name} = field -> {name, field} end)
|> Enum.into(%{})
end
def type(spec)
def type(%Schema{type: "array", items: items}) do
{:array, type(items)}
end
def type(%Schema{type: "boolean"}), do: :boolean
def type(%Schema{type: "integer"}), do: :integer
def type(%Schema{type: "number"}), do: :number
def type(%Schema{type: "string"}), do: :string
def type(%Schema{type: "object"} = schema) do
original_name(schema) || :map
end
def type(%Schema{any_of: any_of}) when is_list(any_of) do
{:union, Enum.map(any_of, &type/1)}
end
def type(%Schema{one_of: one_of}) when is_list(one_of) do
{:union, Enum.map(one_of, &type/1)}
end
def type(%Schema{type: nil}), do: :unknown
end