feat: Add struct conversion code
This commit is contained in:
parent
bef0d41f0e
commit
68cc47ba2e
11 changed files with 293 additions and 83 deletions
86
lib/converter/date_time.ex
Normal file
86
lib/converter/date_time.ex
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
defmodule Kindling.Converter.DateTime do
|
||||||
|
defstruct year: "",
|
||||||
|
month: "",
|
||||||
|
day: "",
|
||||||
|
hour: "",
|
||||||
|
minute: "",
|
||||||
|
second: "",
|
||||||
|
zone: ""
|
||||||
|
|
||||||
|
def parse(value) do
|
||||||
|
value
|
||||||
|
|> String.codepoints()
|
||||||
|
|> do_year(%__MODULE__{})
|
||||||
|
|> set_defaults()
|
||||||
|
|> to_iso_string()
|
||||||
|
|> DateTime.from_iso8601()
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_iso_string(%{
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
zone: zone
|
||||||
|
}) do
|
||||||
|
"#{year}-#{month}-#{day}T#{hour}:#{minute}:#{second}#{zone}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_defaults(%{
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
zone: zone
|
||||||
|
}) do
|
||||||
|
%__MODULE__{
|
||||||
|
year: format(year, "0000"),
|
||||||
|
month: format(month, "01"),
|
||||||
|
day: format(day, "01"),
|
||||||
|
hour: format(hour, "00"),
|
||||||
|
minute: format(minute, "00"),
|
||||||
|
second: format(second, "00"),
|
||||||
|
zone: if(zone == "", do: "Z", else: zone)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format("", default), do: default
|
||||||
|
defp format(string, default), do: String.pad_leading(string, String.length(default), "0")
|
||||||
|
|
||||||
|
def do_year([], data), do: data
|
||||||
|
def do_year(["-" | tail], data), do: do_month(tail, data)
|
||||||
|
def do_year([hd | tail], %{year: year} = data), do: do_year(tail, %{data | year: year <> hd})
|
||||||
|
|
||||||
|
def do_month([], data), do: data
|
||||||
|
def do_month(["-" | tail], data), do: do_day(tail, data)
|
||||||
|
|
||||||
|
def do_month([hd | tail], %{month: month} = data),
|
||||||
|
do: do_month(tail, %{data | month: month <> hd})
|
||||||
|
|
||||||
|
def do_day([], data), do: data
|
||||||
|
def do_day(["T" | tail], data), do: do_hour(tail, data)
|
||||||
|
def do_day([hd | tail], %{day: day} = data), do: do_day(tail, %{data | day: day <> hd})
|
||||||
|
|
||||||
|
def do_hour([], data), do: data
|
||||||
|
def do_hour([":" | tail], data), do: do_minute(tail, data)
|
||||||
|
def do_hour([hd | tail], %{hour: hour} = data), do: do_hour(tail, %{data | hour: hour <> hd})
|
||||||
|
|
||||||
|
def do_minute([], data), do: data
|
||||||
|
def do_minute([":" | tail], data), do: do_second(tail, data)
|
||||||
|
|
||||||
|
def do_minute([hd | tail], %{minute: minute} = data),
|
||||||
|
do: do_minute(tail, %{data | minute: minute <> hd})
|
||||||
|
|
||||||
|
def do_second([], data), do: data
|
||||||
|
def do_second([c | tail], data) when c in ["+", "-", "Z"], do: do_zone(tail, data)
|
||||||
|
|
||||||
|
def do_second([hd | tail], %{second: second} = data),
|
||||||
|
do: do_second(tail, %{data | second: second <> hd})
|
||||||
|
|
||||||
|
def do_zone([], data), do: data
|
||||||
|
def do_zone([hd | tail], %{zone: zone} = data), do: do_zone(tail, %{data | zone: zone <> hd})
|
||||||
|
end
|
43
lib/kindling/client.ex
Normal file
43
lib/kindling/client.ex
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule Kindling.Client do
|
||||||
|
alias Kindling.Converter
|
||||||
|
|
||||||
|
defstruct [:base_url, :access_token, auth_mode: :bearer]
|
||||||
|
|
||||||
|
def read(client, resource_module, id, opts \\ []) do
|
||||||
|
base_uri = URI.parse(client.base_url)
|
||||||
|
uri = base_uri |> URI.append_path(resource_module.path()) |> URI.append_path("/#{id}")
|
||||||
|
more_headers = Keyword.get(opts, :headers, [])
|
||||||
|
|
||||||
|
headers = headers(client, more_headers)
|
||||||
|
|
||||||
|
uri
|
||||||
|
|> Req.get(headers: headers)
|
||||||
|
|> case do
|
||||||
|
{:ok, %{status: status} = response} when status >= 200 and status < 300 ->
|
||||||
|
Converter.convert(resource_module.version(), response.body)
|
||||||
|
|
||||||
|
other ->
|
||||||
|
other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def headers(client, more_headers) do
|
||||||
|
case client.auth_mode do
|
||||||
|
:open ->
|
||||||
|
[format_header() | more_headers]
|
||||||
|
|
||||||
|
_other ->
|
||||||
|
[auth_header(client) | [format_header() | more_headers]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def auth_header(%{auth_mode: :bearer, access_token: token}),
|
||||||
|
do: {"Authorization", "Bearer #{token}"}
|
||||||
|
|
||||||
|
def auth_header(%{auth_mode: :basic, access_token: token}),
|
||||||
|
do: {"Authorization", "Basic #{token}"}
|
||||||
|
|
||||||
|
def format_header do
|
||||||
|
{"Accept", "application/json"}
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,19 +1,3 @@
|
||||||
defmodule Kindling.Config do
|
defmodule Kindling.Config do
|
||||||
@default_embedded_resources [
|
def root_resources, do: Application.get_env(:kindling, :root_resources, ["Encounter"])
|
||||||
"Coding",
|
|
||||||
"Duration",
|
|
||||||
"Extension",
|
|
||||||
"Identifier",
|
|
||||||
"Meta",
|
|
||||||
"Element",
|
|
||||||
"CodeableConcept",
|
|
||||||
"Narrative",
|
|
||||||
"Period",
|
|
||||||
"Reference",
|
|
||||||
"ResourceList"
|
|
||||||
]
|
|
||||||
|
|
||||||
def embedded_resources() do
|
|
||||||
Application.get_env(:kindling, :embedded_resources, @default_embedded_resources)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
89
lib/kindling/converter.ex
Normal file
89
lib/kindling/converter.ex
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
defmodule Kindling.Converter do
|
||||||
|
alias Kindling.Schema.Resource
|
||||||
|
|
||||||
|
def convert(version_namespace, %{"resourceType" => resource_type} = resource_json) do
|
||||||
|
resource_module = Module.concat(version_namespace, Resource.class_name(resource_type))
|
||||||
|
|
||||||
|
structify(resource_module, resource_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
def structify(resource_module, resource_json) do
|
||||||
|
Code.ensure_loaded!(resource_module)
|
||||||
|
|
||||||
|
atom_map =
|
||||||
|
resource_json
|
||||||
|
|> Enum.map(fn {key_string, value} ->
|
||||||
|
key_atom = key_string |> Recase.to_snake() |> String.to_existing_atom()
|
||||||
|
{key_atom, convert_field(resource_module, key_atom, value)}
|
||||||
|
end)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
|
struct!(resource_module, atom_map)
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_field(resource_module, field, value) do
|
||||||
|
cond do
|
||||||
|
field in resource_module.__schema__(:associations) ->
|
||||||
|
convert_association(resource_module, field, value)
|
||||||
|
|
||||||
|
field in resource_module.__schema__(:embeds) ->
|
||||||
|
convert_embed(resource_module, field, value)
|
||||||
|
|
||||||
|
field in resource_module.__schema__(:fields) ->
|
||||||
|
cast_field(resource_module, field, value)
|
||||||
|
|
||||||
|
true ->
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_field(resource_module, field, value) do
|
||||||
|
type = resource_module.__schema__(:type, field)
|
||||||
|
|
||||||
|
do_cast_field(type, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_cast_field(:utc_datetime_usec, value) do
|
||||||
|
case Kindling.Converter.DateTime.parse(value) do
|
||||||
|
{:ok, v, _} ->
|
||||||
|
v
|
||||||
|
|
||||||
|
other ->
|
||||||
|
other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_cast_field(type, value) do
|
||||||
|
case Ecto.Type.cast(type, value) do
|
||||||
|
{:ok, v} ->
|
||||||
|
v
|
||||||
|
|
||||||
|
other ->
|
||||||
|
other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_association(resource_module, field, value) do
|
||||||
|
%{cardinality: cardinality, related: type} = resource_module.__schema__(:association, field)
|
||||||
|
|
||||||
|
case cardinality do
|
||||||
|
:many ->
|
||||||
|
Enum.map(value, &structify(type, &1))
|
||||||
|
|
||||||
|
:one ->
|
||||||
|
structify(type, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_embed(resource_module, field, value) do
|
||||||
|
%{cardinality: cardinality, related: type} = resource_module.__schema__(:embed, field)
|
||||||
|
|
||||||
|
case cardinality do
|
||||||
|
:many ->
|
||||||
|
Enum.map(value, &structify(type, &1))
|
||||||
|
|
||||||
|
:one ->
|
||||||
|
structify(type, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,10 +3,10 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
@fields [
|
@fields [
|
||||||
<%= @all_fields |> Enum.map(fn name -> ~s("#{Recase.to_snake(name)}") end) |> Enum.join(",\n") %>
|
<%= @all_fields |> Enum.map(fn name -> ~s(:#{Recase.to_snake(name)}) end) |> Enum.join(",\n") %>
|
||||||
]
|
]
|
||||||
@required_fields [
|
@required_fields [
|
||||||
<%= @required_fields |> Enum.map(fn name -> ~s("#{Recase.to_snake(name)}") end) |> Enum.join(",\n") %>
|
<%= @required_fields |> Enum.map(fn name -> ~s(:#{Recase.to_snake(name)}) end) |> Enum.join(",\n") %>
|
||||||
]
|
]
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
|
@ -21,11 +21,11 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
||||||
<%= if @properties.enum != [] do %># Enum<% end %>
|
<%= if @properties.enum != [] do %># Enum<% end %>
|
||||||
<%= for {name, df, _} <- @properties.enum do %>field :<%= Recase.to_snake(name) %>, Ecto.Enum, values: <%= inspect(df["enum"] |> Enum.map(&Recase.to_snake(&1)) |> Enum.map(&String.to_atom/1)) %>
|
<%= for {name, df, _} <- @properties.enum do %>field :<%= Recase.to_snake(name) %>, Ecto.Enum, values: <%= inspect(df["enum"] |> Enum.map(&Recase.to_snake(&1)) |> Enum.map(&String.to_atom/1)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @properties.embed_one != [] do %># Embed One<% end %>
|
<%= if @properties.embeds_one != [] do %># Embed One<% end %>
|
||||||
<%= for {name, df, _} <- @properties.embeds_one do %>embed_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
<%= for {name, df, _} <- @properties.embeds_one do %>embeds_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @properties.embed_many != [] do %># Embed Many<% end %>
|
<%= if @properties.embeds_many != [] do %># Embed Many<% end %>
|
||||||
<%= for {name, df, _} <- @properties.embed_many do %>embed_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
<%= for {name, df, _} <- @properties.embeds_many do %>embeds_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @properties.has_one != [] do %># Has One<% end %>
|
<%= if @properties.has_one != [] do %># Has One<% end %>
|
||||||
<%= for {name, df, _} <- @properties.has_one do %>has_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
<%= for {name, df, _} <- @properties.has_one do %>has_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
||||||
|
@ -33,9 +33,6 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
||||||
<%= if @properties.has_many != [] do %># Has Many<% end %>
|
<%= if @properties.has_many != [] do %># Has Many<% end %>
|
||||||
<%= for {name, df, _} <- @properties.has_many do %>has_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>
|
<%= for {name, df, _} <- @properties.has_many do %>has_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @backlinks != [] do %># belongs_to<% end %>
|
|
||||||
<%= for name <- @backlinks do %>belongs_to :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= class_name(name) %>
|
|
||||||
<% end %>
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def base_changeset(data \\ %__MODULE__{}, attrs) do
|
def base_changeset(data \\ %__MODULE__{}, attrs) do
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
defmodule Kindling.Schema do
|
defmodule Kindling.Schema do
|
||||||
alias Kindling.Config
|
|
||||||
alias Kindling.Version
|
alias Kindling.Version
|
||||||
alias Kindling.Schema.Resource
|
alias Kindling.Schema.Resource
|
||||||
|
|
||||||
|
@ -23,7 +22,6 @@ defmodule Kindling.Schema do
|
||||||
|
|
||||||
defs =
|
defs =
|
||||||
schema["definitions"]
|
schema["definitions"]
|
||||||
|> Map.drop(Config.embedded_resources())
|
|
||||||
|> Enum.reduce(schema["definitions"], fn {name, df}, definitions ->
|
|> Enum.reduce(schema["definitions"], fn {name, df}, definitions ->
|
||||||
refs = Resource.refs(df)
|
refs = Resource.refs(df)
|
||||||
|
|
||||||
|
@ -38,10 +36,14 @@ defmodule Kindling.Schema do
|
||||||
Map.put(schema, "definitions", defs)
|
Map.put(schema, "definitions", defs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def refs_recursive(schema, root_name) do
|
def refs_recursive(schema, root_name) when is_binary(root_name) do
|
||||||
do_recurse(schema, MapSet.new([root_name]), [root_name])
|
do_recurse(schema, MapSet.new([root_name]), [root_name])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def refs_recursive(schema, roots) when is_list(roots) do
|
||||||
|
do_recurse(schema, MapSet.new(roots), roots)
|
||||||
|
end
|
||||||
|
|
||||||
defp do_recurse(_schema, visited, []), do: visited
|
defp do_recurse(_schema, visited, []), do: visited
|
||||||
|
|
||||||
defp do_recurse(schema, visited, [hd | tail]) do
|
defp do_recurse(schema, visited, [hd | tail]) do
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
defmodule Kindling.Schema.Resource do
|
defmodule Kindling.Schema.Resource do
|
||||||
alias Kindling.Config
|
@empty_properties %{
|
||||||
|
array: [],
|
||||||
@empty_properties %{array: [], const: [], has_one: [], has_many: [], enum: [], value: []}
|
const: [],
|
||||||
|
embeds_one: [],
|
||||||
|
embeds_many: [],
|
||||||
|
has_one: [],
|
||||||
|
has_many: [],
|
||||||
|
enum: [],
|
||||||
|
value: []
|
||||||
|
}
|
||||||
|
|
||||||
def properties(df) do
|
def properties(df) do
|
||||||
df["properties"]
|
df["properties"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def grouped_properties(df) do
|
def grouped_properties(df, roots) do
|
||||||
properties =
|
properties =
|
||||||
(df["properties"] || %{})
|
(df["properties"] || %{})
|
||||||
|> Map.delete("id")
|
|> Map.delete("id")
|
||||||
|> Enum.reject(&is_element/1)
|
|> Enum.reject(&is_element/1)
|
||||||
|> Enum.map(fn {key, value} -> {key, value, property_type(value)} end)
|
|> Enum.map(fn {key, value} -> {key, value, property_type(value, roots)} end)
|
||||||
|> Enum.group_by(fn {_key, _value, type} ->
|
|> Enum.group_by(fn {_key, _value, type} ->
|
||||||
type
|
type
|
||||||
end)
|
end)
|
||||||
|
@ -23,7 +30,7 @@ defmodule Kindling.Schema.Resource do
|
||||||
def all_fields(df) do
|
def all_fields(df) do
|
||||||
(df["properties"] || %{})
|
(df["properties"] || %{})
|
||||||
|> Enum.filter(fn {_name, definition} ->
|
|> Enum.filter(fn {_name, definition} ->
|
||||||
property_type(definition) in [:array, :enum, :value]
|
property_type(definition, []) in [:array, :enum, :value]
|
||||||
end)
|
end)
|
||||||
|> Enum.map(fn {name, _} -> name end)
|
|> Enum.map(fn {name, _} -> name end)
|
||||||
end
|
end
|
||||||
|
@ -32,7 +39,7 @@ defmodule Kindling.Schema.Resource do
|
||||||
df
|
df
|
||||||
|> required()
|
|> required()
|
||||||
|> Enum.filter(fn {_name, definition} ->
|
|> Enum.filter(fn {_name, definition} ->
|
||||||
property_type(definition) in [:array, :enum, :value]
|
property_type(definition, []) in [:array, :enum, :value]
|
||||||
end)
|
end)
|
||||||
|> Enum.map(fn {name, _} -> name end)
|
|> Enum.map(fn {name, _} -> name end)
|
||||||
end
|
end
|
||||||
|
@ -45,37 +52,35 @@ defmodule Kindling.Schema.Resource do
|
||||||
key in df["required"]
|
key in df["required"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def property_type(%{"const" => _}), do: :const
|
def property_type(%{"const" => _}, _roots), do: :const
|
||||||
|
|
||||||
for embed <- Config.embedded_resources() do
|
def property_type(%{"$ref" => "#/definitions/" <> name}, roots) do
|
||||||
dbg(embed)
|
|
||||||
|
|
||||||
def property_type(%{"$ref" => "#/definitions/" <> unquote(embed)}), do: :embed_one
|
|
||||||
|
|
||||||
def property_type(%{"items" => %{"$ref" => "#/definitions/" <> unquote(embed)}}) do
|
|
||||||
:embed_many
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def property_type(%{"$ref" => "#/definitions/" <> name}) do
|
|
||||||
if is_class_name(name) do
|
if is_class_name(name) do
|
||||||
:has_one
|
if name in roots do
|
||||||
|
:has_one
|
||||||
|
else
|
||||||
|
:embeds_one
|
||||||
|
end
|
||||||
else
|
else
|
||||||
:value
|
:value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def property_type(%{"items" => %{"$ref" => "#/definitions/" <> name}}) do
|
def property_type(%{"items" => %{"$ref" => "#/definitions/" <> name}}, roots) do
|
||||||
if is_class_name(name) do
|
if is_class_name(name) do
|
||||||
:has_many
|
if name in roots do
|
||||||
|
:has_many
|
||||||
|
else
|
||||||
|
:embeds_many
|
||||||
|
end
|
||||||
else
|
else
|
||||||
:array
|
:array
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def property_type(%{"items" => %{"enum" => _}}), do: :array
|
def property_type(%{"items" => %{"enum" => _}}, _roots), do: :array
|
||||||
def property_type(%{"enum" => _}), do: :enum
|
def property_type(%{"enum" => _}, _roots), do: :enum
|
||||||
def property_type(_), do: :value
|
def property_type(_, _roots), do: :value
|
||||||
|
|
||||||
def refs(df) do
|
def refs(df) do
|
||||||
(df["properties"] || %{})
|
(df["properties"] || %{})
|
||||||
|
@ -95,10 +100,16 @@ defmodule Kindling.Schema.Resource do
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_class_name(name) do
|
def is_class_name(name) do
|
||||||
!(name in Config.embedded_resources()) && Regex.match?(~r/^[A-Z]/, name)
|
Regex.match?(~r/^[A-Z]/, name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_element({_name, %{"$ref" => "#/definitions/Element"}}), do: true
|
def is_element({_name, %{"$ref" => "#/definitions/Element"}}), do: true
|
||||||
def is_element({name, %{"items" => items}}), do: is_element({name, items})
|
def is_element({name, %{"items" => items}}), do: is_element({name, items})
|
||||||
def is_element(_), do: false
|
def is_element(_), do: false
|
||||||
|
|
||||||
|
def ref_to_class_name("#/definitions/" <> name),
|
||||||
|
do: class_name(name)
|
||||||
|
|
||||||
|
def class_name(name),
|
||||||
|
do: name |> String.split("_") |> Enum.map(&Recase.to_pascal/1) |> Enum.join(".")
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,10 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
@fields [
|
@fields [
|
||||||
<%= @all_fields |> Enum.map(fn name -> ~s("#{Recase.to_snake(name)}") end) |> Enum.join(",\n") %>
|
<%= @all_fields |> Enum.map(fn name -> ~s(:#{Recase.to_snake(name)}) end) |> Enum.join(",\n") %>
|
||||||
]
|
]
|
||||||
@required_fields [
|
@required_fields [
|
||||||
<%= @required_fields |> Enum.map(fn name -> ~s("#{Recase.to_snake(name)}") end) |> Enum.join(",\n") %>
|
<%= @required_fields |> Enum.map(fn name -> ~s(:#{Recase.to_snake(name)}) end) |> Enum.join(",\n") %>
|
||||||
]
|
]
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
|
@ -22,11 +22,11 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
||||||
<%= if @properties.enum != [] do %># Enum<% end %>
|
<%= if @properties.enum != [] do %># Enum<% end %>
|
||||||
<%= for {name, df, _} <- @properties.enum do %>field :<%= Recase.to_snake(name) %>, Ecto.Enum, values: <%= inspect(df["enum"] |> Enum.map(&Recase.to_snake(&1)) |> Enum.map(&String.to_atom/1)) %>
|
<%= for {name, df, _} <- @properties.enum do %>field :<%= Recase.to_snake(name) %>, Ecto.Enum, values: <%= inspect(df["enum"] |> Enum.map(&Recase.to_snake(&1)) |> Enum.map(&String.to_atom/1)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @properties.embed_one != [] do %># Embed One<% end %>
|
<%= if @properties.embeds_one != [] do %># Embed One<% end %>
|
||||||
<%= for {name, df, _} <- @properties.embed_one do %>embeds_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
<%= for {name, df, _} <- @properties.embeds_one do %>embeds_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @properties.embed_many != [] do %># Embed Many<% end %>
|
<%= if @properties.embeds_many != [] do %># Embed Many<% end %>
|
||||||
<%= for {name, df, _} <- @properties.embed_many do %>embed_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>
|
<%= for {name, df, _} <- @properties.embeds_many do %>embeds_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @properties.has_one != [] do %># Has One<% end %>
|
<%= if @properties.has_one != [] do %># Has One<% end %>
|
||||||
<%= for {name, df, _} <- @properties.has_one do %>has_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
<%= for {name, df, _} <- @properties.has_one do %>has_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
|
||||||
|
@ -39,6 +39,9 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
||||||
<% end %>
|
<% end %>
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def version, do: <%= @namespace %>.<%= @version %>
|
||||||
|
def path, do: "/<%= @resource_name %>"
|
||||||
|
|
||||||
def base_changeset(data \\ %__MODULE__{}, attrs) do
|
def base_changeset(data \\ %__MODULE__{}, attrs) do
|
||||||
data
|
data
|
||||||
|> cast(attrs, @fields)
|
|> cast(attrs, @fields)
|
||||||
|
|
|
@ -2,41 +2,41 @@ defmodule Kindling.Templates do
|
||||||
require EEx
|
require EEx
|
||||||
|
|
||||||
import Kindling.Templates.Functions
|
import Kindling.Templates.Functions
|
||||||
|
import Kindling.Schema.Resource
|
||||||
|
|
||||||
alias Kindling.Config
|
|
||||||
alias Kindling.Schema.Resource
|
alias Kindling.Schema.Resource
|
||||||
|
|
||||||
EEx.function_from_file(:def, :render, "lib/kindling/template.eex", [:assigns])
|
EEx.function_from_file(:def, :render, "lib/kindling/template.eex", [:assigns])
|
||||||
EEx.function_from_file(:def, :render_embedded, "lib/kindling/embed_template.eex", [:assigns])
|
EEx.function_from_file(:def, :render_embedded, "lib/kindling/embed_template.eex", [:assigns])
|
||||||
|
|
||||||
def resource_code(namespace, version, resource_name, resource) do
|
def resource_code(namespace, version, roots, resource_name, resource) do
|
||||||
assigns = %{
|
assigns = %{
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
version: version,
|
version: version,
|
||||||
resource_name: resource_name,
|
resource_name: resource_name,
|
||||||
properties: Resource.grouped_properties(resource),
|
properties: Resource.grouped_properties(resource, roots),
|
||||||
all_fields: Resource.all_fields(resource),
|
all_fields: Resource.all_fields(resource),
|
||||||
required_fields: Resource.required_fields(resource),
|
required_fields: Resource.required_fields(resource),
|
||||||
backlinks: resource["__backlinks"]
|
backlinks: Enum.filter(resource["__backlinks"], &(&1 in roots))
|
||||||
}
|
}
|
||||||
|
|
||||||
if resource_name in Config.embedded_resources() do
|
if resource_name in roots do
|
||||||
assigns
|
assigns
|
||||||
|> render_embedded()
|
|> render()
|
||||||
|> Code.format_string!(file: "#{resource_name}.ex")
|
|> Code.format_string!(file: "#{resource_name}.ex")
|
||||||
else
|
else
|
||||||
assigns
|
assigns
|
||||||
|> render()
|
|> render_embedded()
|
||||||
|> Code.format_string!(file: "#{resource_name}.ex")
|
|> Code.format_string!(file: "#{resource_name}.ex")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_code(namespace, version, resource_name, resource) do
|
def write_code(namespace, version, roots, resource_name, resource) do
|
||||||
dir = Path.join(["lib", String.downcase(namespace), String.downcase(version)])
|
dir = Path.join(["lib", String.downcase(namespace), String.downcase(version)])
|
||||||
file = Path.join([dir | to_source_file_name(resource_name)])
|
file = Path.join([dir | to_source_file_name(resource_name)])
|
||||||
|
|
||||||
File.mkdir_p!(Path.dirname(file))
|
File.mkdir_p!(Path.dirname(file))
|
||||||
File.write!(file, resource_code(namespace, version, resource_name, resource))
|
File.write!(file, resource_code(namespace, version, roots, resource_name, resource))
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_source_file_name(resource_name) do
|
def to_source_file_name(resource_name) do
|
||||||
|
|
|
@ -40,10 +40,4 @@ defmodule Kindling.Templates.Functions do
|
||||||
|
|
||||||
def fhir_type_to_ecto(%{"type" => "array", "items" => items}),
|
def fhir_type_to_ecto(%{"type" => "array", "items" => items}),
|
||||||
do: {:array, fhir_type_to_ecto(items)}
|
do: {:array, fhir_type_to_ecto(items)}
|
||||||
|
|
||||||
def ref_to_class_name("#/definitions/" <> name),
|
|
||||||
do: class_name(name)
|
|
||||||
|
|
||||||
def class_name(name),
|
|
||||||
do: name |> String.split("_") |> Enum.map(&Recase.to_pascal/1) |> Enum.join(".")
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,22 +6,23 @@ defmodule Mix.Tasks.Kindling.GenerateSchemas do
|
||||||
|
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(args) do
|
def run(args) do
|
||||||
if Enum.count(args) != 3 do
|
if Enum.count(args) != 2 do
|
||||||
Mix.shell().error(
|
Mix.shell().error("usage: mix mix kindling.generate_schemas <namespace> <R5|R4|R3>")
|
||||||
"usage: mix mix kindling.generate_schemas <namespace> <R5|R4|R3> <root resource name>"
|
|
||||||
)
|
raise "Argument error."
|
||||||
end
|
end
|
||||||
|
|
||||||
[namespace, version, root] = args
|
[namespace, version] = args
|
||||||
|
|
||||||
schema = Kindling.Schema.schema_object(version)
|
schema = Kindling.Schema.schema_object(version)
|
||||||
|
roots = Kindling.Config.root_resources()
|
||||||
|
|
||||||
to_generate =
|
to_generate =
|
||||||
Kindling.Schema.refs_recursive(schema, root)
|
Kindling.Schema.refs_recursive(schema, roots)
|
||||||
|
|
||||||
Enum.each(
|
Enum.each(
|
||||||
to_generate,
|
to_generate,
|
||||||
&Kindling.Templates.write_code(namespace, version, &1, schema["definitions"][&1])
|
&Kindling.Templates.write_code(namespace, version, roots, &1, schema["definitions"][&1])
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue