Packages
open_api_spex
3.14.0
3.22.3
3.22.2
3.22.1
3.22.0
3.21.5
3.21.4
3.21.3
3.21.2
3.21.1
3.21.0
3.20.1
3.20.0
3.19.1
3.19.0
3.18.3
3.18.2
3.18.1
3.18.0
3.17.3
3.17.2
3.17.1
3.17.0
3.16.4
3.16.3
3.16.2
3.16.1
3.16.0
3.15.0
3.14.0
3.13.0
3.12.0
3.11.0
3.10.0
3.9.0
3.8.0
3.7.0
3.6.0
3.5.2
3.5.1
3.5.0
3.4.0
3.3.0
3.2.1
3.2.0
3.1.0
3.0.0
2.3.1
2.3.0
2.2.0
2.1.1
2.1.0
2.0.0
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
Leverage Open Api Specification 3 (swagger) to document, test, validate and explore your Plug and Phoenix APIs.
Current section
Files
Jump to
Current section
Files
lib/open_api_spex/open_api/decode.ex
defmodule OpenApiSpex.OpenApi.Decode do
@moduledoc "This module exposes functionality to convert an arbitrary map into a OpenApi struct."
alias OpenApiSpex.{
Components,
Contact,
Discriminator,
Encoding,
Example,
ExternalDocumentation,
Header,
Info,
License,
Link,
MediaType,
OAuthFlow,
OAuthFlows,
OpenApi,
Operation,
Parameter,
PathItem,
Reference,
RequestBody,
Response,
Schema,
SecurityScheme,
Server,
ServerVariable,
Tag,
Xml
}
@open_api_spex_extensions ["x-struct", "x-validate"]
def decode(%{"openapi" => _openapi, "info" => _info, "paths" => _paths} = map) do
map
|> to_struct(OpenApi)
|> prop_to_struct(:info, Info)
|> prop_to_struct(:paths, PathItems)
|> prop_to_struct(:servers, Servers)
|> prop_to_struct(:components, Components)
|> prop_to_struct(:tags, Tag)
|> prop_to_struct(:externalDocs, ExternalDocumentation)
|> add_extensions(map)
end
defp struct_from_map(struct, map) when is_atom(struct) do
struct_from_map(struct.__struct__(), map)
end
defp struct_from_map(%_{} = struct, map) do
keys = struct |> Map.from_struct() |> Map.keys()
Enum.reduce(keys, struct, fn key, struct ->
case map_get(map, key) do
{_, value} -> Map.put(struct, key, value)
_ -> struct
end
end)
end
defp map_get(map, atom_key) when is_atom(atom_key) do
case map do
%{^atom_key => value} ->
{atom_key, value}
_ ->
map_get(map, to_string(atom_key))
end
end
defp map_get(map, string_key) when is_binary(string_key) do
case map do
%{^string_key => value} -> {string_key, value}
_ -> nil
end
end
defp embedded_ref_or_struct(list, mod) when is_list(list) do
list
|> Enum.map(fn
%{"$ref" => _} = v -> struct_from_map(Reference, v)
v -> to_struct(v, mod)
end)
end
defp embedded_ref_or_struct(map, mod) when is_map(map) do
map
|> Map.new(fn
{k, %{"$ref" => _} = v} ->
{k, struct_from_map(Reference, v)}
{k, v} ->
{k, to_struct(v, mod)}
end)
end
defp update_map_if_key_present(map, key, fun) do
case map do
%{^key => value} -> %{map | key => fun.(value)}
%{} -> map
end
end
# In some cases, e.g. Schema type we must convert values that are strings to atoms,
# for example Schema.type should be one of:
#
# :string | :number | :integer | :boolean | :array | :object
#
# This function, ensures that if the map has the key — it'll convert the corresponding value
# to an atom.
defp convert_value_to_atom_if_present(map, key) do
update_fn = fn
nil -> nil
s -> String.to_atom(s)
end
update_map_if_key_present(map, key, update_fn)
end
# In some cases, e.g. Schema type we must convert values that are list of strings to a list atoms,
#
# This function, ensures that if the map has the key — it'll convert the corresponding value
# to a list of atoms.
defp convert_value_to_list_of_atoms_if_present(map, key) do
map_fn = &String.to_atom/1
update_map_if_key_present(map, key, &Enum.map(&1, map_fn))
end
# The Schema.type and Schema.required keys require some special treatment since their
# values should be converted to atoms
defp prepare_schema(map) do
map
|> convert_value_to_atom_if_present("type")
|> convert_value_to_atom_if_present("format")
|> convert_value_to_atom_if_present("x-struct")
|> convert_value_to_list_of_atoms_if_present("required")
end
defp to_struct(nil, _mod), do: nil
defp to_struct(tag, Tag) when is_binary(tag), do: tag
defp to_struct(map, Tag) when is_map(map) do
Tag
|> struct_from_map(map)
|> prop_to_struct(:externalDocs, ExternalDocumentation)
|> add_extensions(map)
end
defp to_struct(list, Tag) when is_list(list) do
list
|> Enum.map(&to_struct(&1, Tag))
end
defp to_struct(map, Components) do
Components
|> struct_from_map(map)
|> prop_to_struct(:schemas, Schemas)
|> prop_to_struct(:responses, Responses)
|> prop_to_struct(:parameters, Parameters)
|> prop_to_struct(:examples, Examples)
|> prop_to_struct(:requestBodies, RequestBodies)
|> prop_to_struct(:headers, Headers)
|> prop_to_struct(:securitySchemes, SecuritySchemes)
|> prop_to_struct(:links, Links)
|> prop_to_struct(:callbacks, Callbacks)
|> add_extensions(map)
end
defp to_struct(map, Link) do
Link
|> struct_from_map(map)
|> prop_to_struct(:server, Server)
|> prop_to_struct(:requestBody, RequestBody)
|> prop_to_struct(:parameters, Parameters)
|> add_extensions(map)
end
defp to_struct(map, Links), do: embedded_ref_or_struct(map, Link)
defp to_struct(map, SecurityScheme) do
SecurityScheme
|> struct_from_map(map)
|> prop_to_struct(:flows, OAuthFlows)
|> add_extensions(map)
end
defp to_struct(map, SecuritySchemes), do: embedded_ref_or_struct(map, SecurityScheme)
defp to_struct(map, OAuthFlow) do
OAuthFlow
|> struct_from_map(map)
|> add_extensions(map)
end
defp to_struct(map, OAuthFlows) do
OAuthFlows
|> struct_from_map(map)
|> prop_to_struct(:implicit, OAuthFlow)
|> prop_to_struct(:password, OAuthFlow)
|> prop_to_struct(:clientCredentials, OAuthFlow)
|> prop_to_struct(:authorizationCode, OAuthFlow)
|> add_extensions(map)
end
defp to_struct(%{"$ref" => _} = map, Schema), do: struct_from_map(Reference, map)
defp to_struct(%{"type" => type} = map, Schema)
when type in ~w(number integer boolean string) do
map
|> prepare_schema()
|> (&struct_from_map(Schema, &1)).()
|> prop_to_struct(:xml, Xml)
|> add_extensions(map)
end
defp to_struct(%{"type" => "array"} = map, Schema) do
map
|> prepare_schema()
|> (&struct_from_map(Schema, &1)).()
|> prop_to_struct(:items, Schema)
|> prop_to_struct(:xml, Xml)
|> add_extensions(map)
end
defp to_struct(%{"anyOf" => _valid_schemas} = map, Schema) do
Schema
|> struct_from_map(map)
|> prop_to_struct(:anyOf, Schemas)
|> prop_to_struct(:discriminator, Discriminator)
|> add_extensions(map)
end
defp to_struct(%{"oneOf" => _valid_schemas} = map, Schema) do
Schema
|> struct_from_map(map)
|> prop_to_struct(:oneOf, Schemas)
|> prop_to_struct(:discriminator, Discriminator)
|> add_extensions(map)
end
defp to_struct(%{"allOf" => _valid_schemas} = map, Schema) do
Schema
|> struct_from_map(map)
|> prop_to_struct(:allOf, Schemas)
|> prop_to_struct(:discriminator, Discriminator)
|> add_extensions(map)
end
defp to_struct(%{"type" => "object"} = map, Schema) do
map
|> Map.update("properties", %{}, fn v ->
v
|> Map.new(fn {k, v} ->
{String.to_atom(k), v}
end)
end)
|> prepare_schema()
|> (&struct_from_map(Schema, &1)).()
|> prop_to_struct(:properties, Schemas)
|> manage_additional_properties()
|> prop_to_struct(:externalDocs, ExternalDocumentation)
|> add_extensions(map)
end
defp to_struct(%{"not" => _valid_schemas} = map, Schema) do
Schema
|> struct_from_map(map)
|> prop_to_struct(:not, Schema)
|> add_extensions(map)
end
defp to_struct(map, Schemas) when is_map(map), do: embedded_ref_or_struct(map, Schema)
defp to_struct(list, Schemas) when is_list(list), do: embedded_ref_or_struct(list, Schema)
defp to_struct(map, Callback) do
map
|> Map.new(fn {k, v} ->
{k, to_struct(v, PathItem)}
end)
end
defp to_struct(map_or_list, Callbacks), do: embedded_ref_or_struct(map_or_list, Callback)
defp to_struct(map, Operation) do
Operation
|> struct_from_map(map)
|> prop_to_struct(:tags, Tag)
|> prop_to_struct(:externalDocs, ExternalDocumentation)
|> prop_to_struct(:responses, Responses)
|> prop_to_struct(:parameters, Parameters)
|> prop_to_struct(:requestBody, RequestBody)
|> prop_to_struct(:callbacks, Callbacks)
|> prop_to_struct(:servers, Server)
|> add_extensions(map)
end
defp to_struct(%{"$ref" => _} = map, RequestBody), do: struct_from_map(Reference, map)
defp to_struct(map, RequestBody) do
RequestBody
|> struct_from_map(map)
|> prop_to_struct(:content, Content)
|> add_extensions(map)
end
defp to_struct(map, RequestBodies), do: embedded_ref_or_struct(map, RequestBody)
defp to_struct(map, Parameter) do
map
|> convert_value_to_atom_if_present("name")
|> convert_value_to_atom_if_present("in")
|> convert_value_to_atom_if_present("style")
|> (&struct_from_map(Parameter, &1)).()
|> prop_to_struct(:examples, Examples)
|> prop_to_struct(:content, Content)
|> prop_to_struct(:schema, Schema)
|> add_extensions(map)
end
defp to_struct(map_or_list, Parameters), do: embedded_ref_or_struct(map_or_list, Parameter)
defp to_struct(map, ServerVariable) do
ServerVariable
|> struct_from_map(map)
|> add_extensions(map)
end
defp to_struct(map, ServerVariables) do
map
|> Map.new(fn {k, v} ->
{k, to_struct(v, ServerVariable)}
end)
end
defp to_struct(map, Server) do
Server
|> struct_from_map(map)
|> prop_to_struct(:variables, ServerVariables)
|> add_extensions(map)
end
defp to_struct(list, Servers) when is_list(list) do
Enum.map(list, &to_struct(&1, Server))
end
defp to_struct(map, Response) do
Response
|> struct_from_map(map)
|> prop_to_struct(:headers, Headers)
|> prop_to_struct(:content, Content)
|> prop_to_struct(:links, Links)
|> add_extensions(map)
end
defp to_struct(map, Responses), do: embedded_ref_or_struct(map, Response)
defp to_struct(map, MediaType) do
MediaType
|> struct_from_map(map)
|> prop_to_struct(:examples, Examples)
|> prop_to_struct(:encoding, Encoding)
|> prop_to_struct(:schema, Schema)
|> add_extensions(map)
end
defp to_struct(map, Content) do
map
|> Map.new(fn {k, v} ->
{k, to_struct(v, MediaType)}
end)
end
defp to_struct(map, Encoding) do
map
|> Map.new(fn {k, v} ->
{k,
Encoding
|> struct_from_map(v)
|> convert_value_to_atom_if_present(:style)
|> prop_to_struct(:headers, Headers)
|> add_extensions(v)}
end)
end
defp to_struct(map, Example), do: Example |> struct_from_map(map) |> add_extensions(map)
defp to_struct(map_or_list, Examples), do: embedded_ref_or_struct(map_or_list, Example)
defp to_struct(map, Header) do
Header
|> struct_from_map(map)
|> prop_to_struct(:schema, Schema)
|> add_extensions(map)
end
defp to_struct(map, Headers), do: embedded_ref_or_struct(map, Header)
defp to_struct(map, PathItem) do
PathItem
|> struct_from_map(map)
|> prop_to_struct(:delete, Operation)
|> prop_to_struct(:get, Operation)
|> prop_to_struct(:head, Operation)
|> prop_to_struct(:options, Operation)
|> prop_to_struct(:patch, Operation)
|> prop_to_struct(:post, Operation)
|> prop_to_struct(:put, Operation)
|> prop_to_struct(:trace, Operation)
|> prop_to_struct(:parameters, Parameters)
|> prop_to_struct(:servers, Servers)
|> add_extensions(map)
end
defp to_struct(map, PathItems) do
map
|> Map.new(fn {k, v} ->
{k, to_struct(v, PathItem)}
end)
end
defp to_struct(map, Info) do
Info
|> struct_from_map(map)
|> prop_to_struct(:contact, Contact)
|> prop_to_struct(:license, License)
|> add_extensions(map)
end
defp to_struct(map, mod)
when mod in [License, Contact, ExternalDocumentation, Discriminator, Xml],
do: mod |> struct_from_map(map) |> add_extensions(map)
defp to_struct(list, mod) when is_list(list) and is_atom(mod),
do: Enum.map(list, &to_struct(&1, mod))
defp to_struct(map, module) when is_map(map) and is_atom(module),
do: struct_from_map(module, map)
defp prop_to_struct(map, key, mod) when is_map(map) and is_atom(key) and is_atom(mod) do
Map.update!(map, key, fn v ->
to_struct(v, mod)
end)
end
# additionalProperties is a reference
defp manage_additional_properties(%_{additionalProperties: %{"$ref" => reference}} = map) do
Map.put(map, :additionalProperties, %Reference{"$ref": reference})
end
# additionalProperties is a boolean without validation
defp manage_additional_properties(%_{additionalProperties: value} = map) when is_boolean(value),
do: map
# additionalProperties with custom one off validation
defp manage_additional_properties(%_{additionalProperties: %{} = props} = map) do
Map.put(map, :additionalProperties, to_struct(props, Schema))
end
defp manage_additional_properties(map), do: map
defp add_extensions(struct, map) do
extensions =
map
|> Enum.filter(fn {key, _val} ->
String.starts_with?(key, "x-") and key not in @open_api_spex_extensions
end)
|> Map.new()
Map.put(struct, :extensions, if(map_size(extensions) == 0, do: nil, else: extensions))
end
end