diff --git a/lib/schema/cache.ex b/lib/schema/cache.ex index 4d5a6df..52a10c2 100644 --- a/lib/schema/cache.ex +++ b/lib/schema/cache.ex @@ -927,6 +927,9 @@ defmodule Schema.Cache do |> put_in([:attributes, :category_uid, :_source], name) end + # Adds :_source key to each attribute of item. This must be done before processing (compiling) + # inheritance and patching (resolve_extends and patch_type) since after that processing the + # original source is lost. defp attribute_source({item_key, item}) do item = Map.update( @@ -942,22 +945,19 @@ defmodule Schema.Cache do {key, nil} {key, attribute} -> - attribute = Map.put(attribute, :_source, item_key) - - attribute = - if patch_extends?(item) do - # TODO: HACK. This is part of the code compensating for the - # Schema.Cache.patch_type processing. An attribute _source for an - # extension class or object that uses this patch mechanism - # keeps the form "/" form, which doesn't refer to - # anything after the patch_type processing. This requires a deeper change - # to fix, so here we just keep an extra _source_patched key. - Map.put(attribute, :_source_patched, String.to_atom(patch_name(item))) - else + if patch_extends?(item) do + # Because attribute_source done before patching with patch_type, we need to + # capture the final "patched" type for use by the UI when displaying the source. + # Other uses of :_source require the original pre-patched source. + { + key, attribute - end - - {key, attribute} + |> Map.put(:_source, item_key) + |> Map.put(:_source_patched, String.to_atom(item[:extends])) + } + else + {key, Map.put(attribute, :_source, item_key)} + end end ) end @@ -968,11 +968,24 @@ defmodule Schema.Cache do defp patch_type(items, kind) do Enum.reduce(items, %{}, fn {key, item}, acc -> + # Logger.debug(fn -> + # patching? = + # if patch_extends?(item) do + # " PATCHING:" + # else + # "not patching:" + # end + + # "#{patching?} key #{inspect(key)}," <> + # " name #{inspect(item[:name])}, extends #{inspect(item[:extends])}," <> + # " caption #{inspect(item[:caption])}, extension #{inspect(item[:extension])}" + # end) + if patch_extends?(item) do # This is an extension class or object with the same name its own base class # (The name is not prefixed with the extension name, unlike a key / uid.) - name = patch_name(item) + name = item[:extends] base_key = String.to_atom(name) @@ -1010,14 +1023,30 @@ defmodule Schema.Cache do end) end - defp patch_name(item) do - item[:name] || item[:extends] - end - # Is this item a special patch extends definition as done by patch_type. # It is triggered by a class or object that has no name or the name is the same as the extends. defp patch_extends?(item) do - patch_name(item) == item[:extends] + extends = item[:extends] + + if extends do + name = item[:name] + + if name do + if name == extends do + # name and extends are the same + true + else + # true if name matches extends without extension scope ("extension/name" -> "name") + # This when an item in one extension patches an item in another. + name == Utils.descope(extends) + end + else + # extends with no name + true + end + else + false + end end defp patch_constraints(base, item) do diff --git a/lib/schema_web/views/page_view.ex b/lib/schema_web/views/page_view.ex index eb178d5..e2e2b68 100644 --- a/lib/schema_web/views/page_view.ex +++ b/lib/schema_web/views/page_view.ex @@ -230,6 +230,13 @@ defmodule SchemaWeb.PageView do end end + defp get_hierarchy_source(field) do + # In the case of an attribute from a patched class or object, we want to display the final + # compiled type, which is in :_source_patched and in this case :_source contains the name of + # the pre-patched item. + field[:_source_patched] || field[:_source] + end + # Build a class or object hierarchy path from item_key to target_parent_item_key. # Returns {true, path} when a path is found, and {false, []} otherwise. @spec build_hierarchy(atom(), atom(), map(), list()) :: {boolean(), list()} @@ -261,17 +268,6 @@ defmodule SchemaWeb.PageView do end end - defp get_hierarchy_source(field) do - # TODO: HACK. This is part of the code compensating for the - # Schema.Cache.patch_type processing. An attribute _source for an - # extension class or object that uses this patch mechanism - # keeps the form "/" form, which doesn't refer to - # anything after the patch_type processing. This requires a deeper change - # to fix, so here we just keep an extra _source_patched key. - # Use "fixed" :_source_patched if available - field[:_source_patched] || field[:_source] - end - defp format_hierarchy(path, all_items, kind) do Enum.map( path, diff --git a/mix.exs b/mix.exs index 7b165cd..35f72d8 100644 --- a/mix.exs +++ b/mix.exs @@ -10,7 +10,7 @@ defmodule Schema.MixProject do use Mix.Project - @version "2.73.0" + @version "2.73.1" def project do build = System.get_env("GITHUB_RUN_NUMBER") || "SNAPSHOT" diff --git a/mix.lock b/mix.lock index d777f8d..5c66588 100644 --- a/mix.lock +++ b/mix.lock @@ -23,11 +23,11 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"}, } diff --git a/test/test_ocsf_schema/extensions2/rpg2/extension.json b/test/test_ocsf_schema/extensions2/rpg2/extension.json new file mode 100644 index 0000000..453104d --- /dev/null +++ b/test/test_ocsf_schema/extensions2/rpg2/extension.json @@ -0,0 +1,7 @@ +{ + "caption": "RPG 2", + "description": "The second RPG extension extends the RPG extension.", + "name": "rpg2", + "uid": 43, + "version": "0.1.0-rpg2-test" +} diff --git a/test/test_ocsf_schema/extensions2/rpg2/objects/device_(patch).json b/test/test_ocsf_schema/extensions2/rpg2/objects/device_(patch).json new file mode 100644 index 0000000..07c7f87 --- /dev/null +++ b/test/test_ocsf_schema/extensions2/rpg2/objects/device_(patch).json @@ -0,0 +1,10 @@ +{ + "caption": "RPG 2 Device", + "description": "Patch extends of Device for the RPG 2 extension.", + "extends": "device", + "attributes": { + "rpg/hp": { + "requirement": "optional" + } + } +} diff --git a/test/test_ocsf_schema/extensions2/rpg2/objects/hammer_(patch).json b/test/test_ocsf_schema/extensions2/rpg2/objects/hammer_(patch).json new file mode 100644 index 0000000..701ff79 --- /dev/null +++ b/test/test_ocsf_schema/extensions2/rpg2/objects/hammer_(patch).json @@ -0,0 +1,9 @@ +{ + "caption": "RPG 2 Hammer Patch", + "extends": "rpg/hammer", + "attributes": { + "rpg/hp": { + "requirement": "optional" + } + } +} diff --git a/test/test_ocsf_schema/extensions2/rpg2/objects/sword.json b/test/test_ocsf_schema/extensions2/rpg2/objects/sword.json new file mode 100644 index 0000000..aab4cd5 --- /dev/null +++ b/test/test_ocsf_schema/extensions2/rpg2/objects/sword.json @@ -0,0 +1,20 @@ +{ + "caption": "Sword", + "description": "The Sword object represents a stabby instrument of death.", + "name": "sword", + "attributes": { + "desc": { + "caption": "Description", + "description": "The description of the sword, ordinarily as reported by the blacksmith.", + "requirement": "optional" + }, + "name": { + "description": "The sword's name. Named swords are just cooler.", + "requirement": "recommended" + }, + "rpg/damage_range": { + "description": "The amount of damage that could be inflicted by this sword.", + "requirement": "recommended" + } + } +}