feat: Start XML support
This commit is contained in:
parent
9e9c45aee1
commit
4fc2b745e4
10 changed files with 195 additions and 15 deletions
|
@ -49,7 +49,10 @@ defmodule Kindling.Client do
|
|||
|> req_fn.(headers: headers)
|
||||
|> case do
|
||||
{:ok, %{status: status} = response} when status >= 200 and status < 300 ->
|
||||
Converter.convert(resource_module.version_namespace(), response.body)
|
||||
{:ok, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
{:ok, %{body: %{"resourceType" => _}} = response} ->
|
||||
{:error, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
other ->
|
||||
other
|
||||
|
@ -86,13 +89,86 @@ defmodule Kindling.Client do
|
|||
|> req_fn.(headers: headers)
|
||||
|> case do
|
||||
{:ok, %{status: status} = response} when status >= 200 and status < 300 ->
|
||||
Converter.convert(resource_module.version_namespace(), response.body)
|
||||
{:ok, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
{:ok, %{body: %{"resourceType" => _}} = response} ->
|
||||
{:error, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
def create(client, resource_module, attrs, opts \\ [], req_fn \\ &Kindling.Client.Req.post/2) do
|
||||
base_uri = URI.parse(client.base_url)
|
||||
uri = base_uri |> URI.append_path(resource_module.path())
|
||||
more_headers = Keyword.get(opts, :headers, [])
|
||||
|
||||
headers = headers(client, more_headers)
|
||||
|
||||
uri
|
||||
|> req_fn.(headers: headers, json: attrs)
|
||||
|> case do
|
||||
{:ok, %{status: status} = response} when status >= 200 and status < 300 ->
|
||||
{:ok, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
{:ok, %{body: %{"resourceType" => _}} = response} ->
|
||||
{:error, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
def update(client, resource_module, id, attrs, opts \\ [], req_fn \\ &Kindling.Client.Req.put/2) 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_fn.(headers: headers, json: attrs)
|
||||
|> case do
|
||||
{:ok, %{status: status} = response} when status >= 200 and status < 300 ->
|
||||
{:ok, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
{:ok, %{body: %{"resourceType" => _}} = response} ->
|
||||
{:error, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
def delete(client, resource_module, id, opts \\ [], req_fn \\ &Kindling.Client.Req.delete/2) 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_fn.(headers: headers)
|
||||
|> convert_response(resource_module)
|
||||
end
|
||||
|
||||
def convert_response({:ok, %{status: status}}, _resource_module)
|
||||
when status == 204 do
|
||||
:ok
|
||||
end
|
||||
|
||||
def convert_response({:ok, %{status: status} = response}, resource_module)
|
||||
when status >= 200 and status < 300 do
|
||||
{:ok, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
end
|
||||
|
||||
def convert_response({:ok, %{body: %{"resourceType" => _}} = response}, resource_module) do
|
||||
{:error, Converter.convert(resource_module.version_namespace(), response.body)}
|
||||
end
|
||||
|
||||
def convert_response(other, _), do: other
|
||||
|
||||
@doc false
|
||||
def headers(client, more_headers) do
|
||||
case client.auth_mode do
|
||||
|
|
|
@ -2,4 +2,16 @@ defmodule Kindling.Client.Req do
|
|||
def get(request, opts \\ []) do
|
||||
Req.get(request, opts)
|
||||
end
|
||||
|
||||
def post(request, opts \\ []) do
|
||||
Req.post(request, opts)
|
||||
end
|
||||
|
||||
def put(request, opts \\ []) do
|
||||
Req.put(request, opts)
|
||||
end
|
||||
|
||||
def delete(request, opts \\ []) do
|
||||
Req.delete(request, opts)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,11 +42,24 @@ defmodule Kindling.Converter do
|
|||
|
||||
atom_map =
|
||||
resource_json
|
||||
|> Enum.map(fn {key_string, value} ->
|
||||
|> map_or_nil(fn {key_string, value} ->
|
||||
key_atom = key_string |> Recase.to_snake() |> String.to_existing_atom()
|
||||
{key_atom, convert_field(resource_module, resource_list_module, key_atom, value)}
|
||||
|
||||
case value do
|
||||
nil ->
|
||||
{key_atom, nil}
|
||||
|
||||
value ->
|
||||
{key_atom, convert_field(resource_module, resource_list_module, key_atom, value)}
|
||||
end
|
||||
end)
|
||||
|> Map.new()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
enum ->
|
||||
Map.new(enum)
|
||||
end
|
||||
|
||||
struct!(resource_module, atom_map)
|
||||
end
|
||||
|
@ -85,6 +98,16 @@ defmodule Kindling.Converter do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_cast_field({:array, _} = type, value) do
|
||||
case Ecto.Type.cast(type, wrap(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} ->
|
||||
|
@ -101,7 +124,7 @@ defmodule Kindling.Converter do
|
|||
|
||||
case cardinality do
|
||||
:many ->
|
||||
Enum.map(value, &structify(type, resource_list_module, &1))
|
||||
map_or_nil(wrap(value), &structify(type, resource_list_module, &1))
|
||||
|
||||
:one ->
|
||||
structify(type, resource_list_module, value)
|
||||
|
@ -114,10 +137,20 @@ defmodule Kindling.Converter do
|
|||
|
||||
case cardinality do
|
||||
:many ->
|
||||
Enum.map(value, &structify(type, resource_list_module, &1))
|
||||
map_or_nil(wrap(value), &structify(type, resource_list_module, &1))
|
||||
|
||||
:one ->
|
||||
structify(type, resource_list_module, value)
|
||||
end
|
||||
end
|
||||
|
||||
defp map_or_nil(nil, _), do: nil
|
||||
|
||||
defp map_or_nil(enum, mapper) do
|
||||
Enum.map(enum, mapper)
|
||||
end
|
||||
|
||||
defp wrap(binary) when is_binary(binary), do: [binary]
|
||||
defp wrap(list) when is_list(list), do: list
|
||||
defp wrap(other), do: [other]
|
||||
end
|
||||
|
|
|
@ -13,6 +13,12 @@ defmodule Kindling.Converter.DateTime do
|
|||
second: "",
|
||||
zone: ""
|
||||
|
||||
def parse!(value) do
|
||||
{:ok, v, 0} = parse(value)
|
||||
|
||||
v
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parse a string containing a FHIR-style datetime. Return `{:ok, DateTime.t(), integer()}` or a parse
|
||||
error.
|
||||
|
|
|
@ -38,13 +38,17 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
|||
def version_namespace, do: <%= @namespace %>.<%= @version %>
|
||||
def version, do: "<%= @version %>"
|
||||
|
||||
def base_changeset(data \\ %__MODULE__{}, attrs) do
|
||||
def changeset(data \\ %__MODULE__{}, attrs) do
|
||||
data
|
||||
|> cast(attrs, @fields)
|
||||
<%= for {name, df, _} <- @properties.has_one do %>|> cast_assoc(:<%= Recase.to_snake(name) %>, with: &<%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>.base_changeset/1)
|
||||
<% end %>
|
||||
<%= for {name, df, _} <- @properties.has_many do %>|> cast_assoc(:<%= Recase.to_snake(name) %>, with: &<%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>.base_changeset/1)
|
||||
<% end %>
|
||||
<%= for {name, _df, _} <- @properties.embeds_one do %>|> cast_embed(:<%= Recase.to_snake(name) %>)
|
||||
<% end %>
|
||||
<%= for {name, _df, _} <- @properties.embeds_many do %>|> cast_embed(:<%= Recase.to_snake(name) %>)
|
||||
<% end %>
|
||||
|> validate_required(@required_fields)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,13 +43,17 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do
|
|||
def version, do: "<%= @version %>"
|
||||
def path, do: "/<%= @resource_name %>"
|
||||
|
||||
def base_changeset(data \\ %__MODULE__{}, attrs) do
|
||||
def changeset(data \\ %__MODULE__{}, attrs) do
|
||||
data
|
||||
|> cast(attrs, @fields)
|
||||
<%= for {name, df, _} <- @properties.has_one do %>|> cast_assoc(:<%= Recase.to_snake(name) %>, with: &<%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>.base_changeset/1)
|
||||
<% end %>
|
||||
<%= for {name, df, _} <- @properties.has_many do %>|> cast_assoc(:<%= Recase.to_snake(name) %>, with: &<%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>.base_changeset/1)
|
||||
<% end %>
|
||||
<%= for {name, _df, _} <- @properties.embeds_one do %>|> cast_embed(:<%= Recase.to_snake(name) %>)
|
||||
<% end %>
|
||||
<%= for {name, _df, _} <- @properties.embeds_many do %>|> cast_embed(:<%= Recase.to_snake(name) %>)
|
||||
<% end %>
|
||||
|> validate_required(@required_fields)
|
||||
end
|
||||
end
|
||||
|
|
47
lib/kindling/xml.ex
Normal file
47
lib/kindling/xml.ex
Normal file
|
@ -0,0 +1,47 @@
|
|||
defmodule Kindling.Xml do
|
||||
def parse(doc) do
|
||||
case Saxy.SimpleForm.parse_string(doc) do
|
||||
{:ok, tree} ->
|
||||
process(tree)
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
def process({resource_type, _, children}) do
|
||||
base = %{
|
||||
"resourceType" => resource_type
|
||||
}
|
||||
|
||||
Enum.reduce(children, base, &reduce_child/2)
|
||||
end
|
||||
|
||||
def reduce_child(binary, acc) when is_binary(binary), do: acc
|
||||
|
||||
def reduce_child({"div", _subattrs, _children}, acc), do: acc
|
||||
|
||||
def reduce_child({attribute_name, subattrs, children}, acc) do
|
||||
value =
|
||||
case subattrs do
|
||||
[{"value", value}] ->
|
||||
value
|
||||
|
||||
list when is_list(list) ->
|
||||
attrs = Enum.into(list, %{})
|
||||
|
||||
Enum.reduce(children, attrs, &reduce_child/2)
|
||||
end
|
||||
|
||||
case Map.get(acc, attribute_name) do
|
||||
nil ->
|
||||
Map.put(acc, attribute_name, value)
|
||||
|
||||
list when is_list(list) ->
|
||||
Map.put(acc, attribute_name, list ++ [value])
|
||||
|
||||
other ->
|
||||
Map.put(acc, attribute_name, [other, value])
|
||||
end
|
||||
end
|
||||
end
|
5
mix.exs
5
mix.exs
|
@ -43,11 +43,12 @@ defmodule Kindling.MixProject do
|
|||
defp deps do
|
||||
[
|
||||
{:credo, "~> 1.7", only: [:dev]},
|
||||
{:ecto, "~> 3.11", only: [:dev, :test]},
|
||||
{:ecto, "~> 3.11"},
|
||||
{:ex_doc, "~> 0.31.2", only: [:dev]},
|
||||
{:jason, "~> 1.4"},
|
||||
{:recase, "~> 0.7.0"},
|
||||
{:req, "~> 0.4.11"}
|
||||
{:req, "~> 0.4.11"},
|
||||
{:saxy, "~> 1.5.0"}
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
]
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -21,5 +21,6 @@
|
|||
"nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
|
||||
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
|
||||
"req": {:hex, :req, "0.4.11", "cb19f87d5251e7de30cfc67d1899696b290711092207c6b2e8fc2294f237fcdc", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbf4f2393c649fa4146a3b8470e2a7e8c9b23e4100a16c75f5e7d1d3d33144f3"},
|
||||
"saxy": {:hex, :saxy, "1.5.0", "0141127f2d042856f135fb2d94e0beecda7a2306f47546dbc6411fc5b07e28bf", [:mix], [], "hexpm", "ea7bb6328fbd1f2aceffa3ec6090bfb18c85aadf0f8e5030905e84235861cf89"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
defmodule KindlingTest do
|
||||
use ExUnit.Case
|
||||
doctest Kindling
|
||||
|
||||
test "greets the world" do
|
||||
assert Kindling.hello() == :world
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue