From dee77ae424e59dd29a26c309b11be1023649e2d0 Mon Sep 17 00:00:00 2001 From: Robert Prehn <3952444+prehnRA@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:02:05 -0500 Subject: [PATCH] feat: Add metadata about polymorphic fields --- lib/kindling/embed_template.eex | 8 +++ lib/kindling/schema.ex | 61 ++++++++++++++++++++++ lib/kindling/schema_downloader.ex | 39 +++++++++++++- lib/kindling/template.eex | 8 +++ lib/kindling/templates.ex | 3 +- lib/mix/tasks/kindling/generate_schemas.ex | 1 + 6 files changed, 118 insertions(+), 2 deletions(-) diff --git a/lib/kindling/embed_template.eex b/lib/kindling/embed_template.eex index 0c65d20..003c833 100644 --- a/lib/kindling/embed_template.eex +++ b/lib/kindling/embed_template.eex @@ -35,6 +35,14 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do <% end %> end + <%= for {name, choices} <- @choices do %> + def choices(<%= inspect(name) %>) do + [<%= Enum.map_join(choices, ", ", &":#{&1}") %>] + end + <% end %> + + def choices(_), do: nil + def version_namespace, do: <%= @namespace %>.<%= @version %> def version, do: "<%= @version %>" diff --git a/lib/kindling/schema.ex b/lib/kindling/schema.ex index c7363a0..5a331fa 100644 --- a/lib/kindling/schema.ex +++ b/lib/kindling/schema.ex @@ -18,11 +18,21 @@ defmodule Kindling.Schema do @spec schema_map(version_string()) :: map() def schema_map(version) do filename = Path.join(Version.version_dir(version), "fhir.schema.json") + choices = get_choice_data(version) filename |> File.read!() |> Jason.decode!() |> build_backlinks() + |> add_choice_data(choices) + end + + def get_choice_data(version) do + filename = Path.join(Version.version_dir(version), "choice-elements.json") + + filename + |> File.read!() + |> Jason.decode!() end def all_resources(schema) do @@ -57,6 +67,57 @@ defmodule Kindling.Schema do Map.put(schema, "definitions", defs) end + def add_choice_data(schema, %{"elements" => choices}) do + Enum.reduce(choices, schema, &apply_choice/2) + end + + defp apply_choice({path, types_list}, schema) do + [type_name | rest] = String.split(path, ".") + + {type_name, field_name} = follow_refs(schema, type_name, rest) + field_name = String.replace(field_name, "[x]", "") + type = schema["definitions"][type_name] + + types_list = + case types_list do + ["*"] -> + type["properties"] + |> Map.keys() + |> Enum.filter(&String.starts_with?(&1, field_name)) + |> Enum.map(&String.replace(&1, field_name, "")) + + other -> + other + end + + field_names = + Enum.map(types_list, fn type -> + "#{field_name}_#{Recase.to_snake(type)}" + end) + + existing_choices = Map.get(type, "__choices", %{}) + new_choices = Map.put(existing_choices, field_name, field_names) + + put_in(schema, ["definitions", type_name, "__choices"], new_choices) + end + + defp follow_refs(_schema, type_name, [field_name]), do: {type_name, field_name} + + defp follow_refs(schema, type_name, [hd | rest]) do + ref = + case schema["definitions"][type_name]["properties"][hd] do + %{"items" => %{"$ref" => ref}} -> + ref + + %{"$ref" => ref} -> + ref + end + + "#/definitions/" <> next_type = ref + + follow_refs(schema, next_type, rest) + end + @doc """ Given a schema map in the style returned by `&schema_map/1`, and a root resource name (e.g. `"Encounter"`), return a `MapSet` of resources that are referenced (recursively) from the diff --git a/lib/kindling/schema_downloader.ex b/lib/kindling/schema_downloader.ex index dd3bbb6..d052d4e 100644 --- a/lib/kindling/schema_downloader.ex +++ b/lib/kindling/schema_downloader.ex @@ -29,7 +29,6 @@ defmodule Kindling.SchemaDownloader do :ok other -> - dbg(other) other end end @@ -53,4 +52,42 @@ defmodule Kindling.SchemaDownloader do raise "Could not download and unzip the FHIR schema for version #{version}." end end + + def download_choices(version) do + {:ok, _} = Application.ensure_all_started(:req) + version_alias = Map.get(@version_aliases, version, version) + + version_dir = "#{Mix.Project.build_path(Mix.Project.config())}/fhir/#{version}" + filename = "#{version_dir}/choice-elements.json" + File.mkdir_p!(version_dir) + + Req.Request.new( + method: :get, + url: "http://www.hl7.org/fhir/#{version_alias}/choice-elements.json" + ) + |> Req.Request.prepend_response_steps(handle_utf16: &handle_utf16/1) + |> Req.Request.append_response_steps(decompress_body: &Req.Steps.decompress_body/1) + |> Req.get!() + |> then(&File.write!(filename, &1.body)) + end + + def handle_utf16({request, %{body: "\uFEFF" <> body} = response}) do + {request, %{response | body: body}} + end + + def ensure_choices!(version) do + version_dir = "#{Mix.Project.build_path(Mix.Project.config())}/fhir/#{version}" + filename = "#{version_dir}/choice-elements.json" + + cond do + File.exists?(filename) -> + :ok + + download_choices(version) == :ok -> + :ok + + true -> + raise "Could not download choice element data for version #{version}." + end + end end diff --git a/lib/kindling/template.eex b/lib/kindling/template.eex index d713f6f..51b060b 100644 --- a/lib/kindling/template.eex +++ b/lib/kindling/template.eex @@ -39,6 +39,14 @@ defmodule <%= @namespace %>.<%= @version %>.<%= class_name(@resource_name) %> do <% end %> end + <%= for {name, choices} <- @choices do %> + def choices(<%= inspect(name) %>) do + [<%= Enum.map_join(choices, ", ", &":#{&1}") %>] + end + <% end %> + + def choices(_), do: nil + def version_namespace, do: <%= @namespace %>.<%= @version %> def version, do: "<%= @version %>" def path, do: "/<%= @resource_name %>" diff --git a/lib/kindling/templates.ex b/lib/kindling/templates.ex index 34ce06a..5883c26 100644 --- a/lib/kindling/templates.ex +++ b/lib/kindling/templates.ex @@ -21,7 +21,8 @@ defmodule Kindling.Templates do properties: Resource.grouped_properties(resource, roots), all_fields: Resource.all_fields(resource), required_fields: Resource.required_fields(resource), - backlinks: Enum.filter(resource["__backlinks"], &(&1 in roots)) + backlinks: Enum.filter(resource["__backlinks"], &(&1 in roots)), + choices: resource["__choices"] || %{} } if resource_name in roots do diff --git a/lib/mix/tasks/kindling/generate_schemas.ex b/lib/mix/tasks/kindling/generate_schemas.ex index da75f57..22e2df8 100644 --- a/lib/mix/tasks/kindling/generate_schemas.ex +++ b/lib/mix/tasks/kindling/generate_schemas.ex @@ -15,6 +15,7 @@ defmodule Mix.Tasks.Kindling.GenerateSchemas do [namespace, version] = args Kindling.SchemaDownloader.ensure_version!(version) + Kindling.SchemaDownloader.ensure_choices!(version) schema = Kindling.Schema.schema_map(version) roots = Kindling.Config.root_resources()